diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md index 5272ba930d59..e0321a6056af 100644 --- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.8 + +* Changes method channels to pigeon. + ## 1.0.7 * Adds pub topics to package metadata. diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj index 395847ae2de1..ed64aa55762d 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -16,12 +16,9 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; }; E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; }; E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */; }; - E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */; }; E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; }; - E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -78,12 +75,9 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = ""; }; E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = ""; }; E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemProvider.swift; sourceTree = ""; }; - E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutItemParserTests.swift; sourceTree = ""; }; E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; }; - E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemParser.swift; sourceTree = ""; }; F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -120,7 +114,6 @@ E092A7F228D10908005C7F67 /* Mocks */, 33E20B3626EFCDFC00A4A191 /* Info.plist */, E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */, - E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -215,9 +208,7 @@ E092A7F228D10908005C7F67 /* Mocks */ = { isa = PBXGroup; children = ( - E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */, E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */, - E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */, ); path = Mocks; sourceTree = ""; @@ -289,7 +280,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33E20B3126EFCDFC00A4A191 = { @@ -364,6 +355,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -435,10 +427,7 @@ buildActionMask = 2147483647; files = ( E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */, - E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */, E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */, - E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */, - E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1ba2b47c79f1..2810c229f3a0 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Void)? = nil - func invokeMethod(_ method: String, arguments: Any?) { - invokeMethodStub?(method, arguments) - } -} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift deleted file mode 100644 index 3b5a09653958..000000000000 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import Foundation - -@testable import quick_actions_ios - -final class MockShortcutItemParser: ShortcutItemParser { - - var parseShortcutItemsStub: ((_ items: [[String: Any]]) -> [UIApplicationShortcutItem])? = nil - - func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] { - return parseShortcutItemsStub?(items) ?? [] - } -} diff --git a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift index 268a89ba5a5b..f0dff1650b72 100644 --- a/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift +++ b/packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift @@ -7,14 +7,27 @@ import XCTest @testable import quick_actions_ios +class MockFlutterApi: IOSQuickActionsFlutterApiProtocol { + /// Method to allow for async testing. + var launchActionCallback: ((String) -> Void)? = nil + + func launchAction( + action actionArg: String, completion: @escaping (Result) -> Void + ) { + self.launchActionCallback?(actionArg) + completion(.success(Void())) + } +} + class QuickActionsPluginTests: XCTestCase { func testHandleMethodCall_setShortcutItems() { - let rawItem = [ - "type": "SearchTheThing", - "localizedTitle": "Search the thing", - "icon": "search_the_thing.png", - ] + let rawItem = ShortcutItemMessage( + type: "SearchTheThing", + localizedTitle: "Search the thing", + icon: "search_the_thing.png" + ) + let item = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", @@ -22,32 +35,15 @@ class QuickActionsPluginTests: XCTestCase { icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) - let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem]) - - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) - - let parseShortcutItemsExpectation = expectation( - description: "parseShortcutItems must be called.") - mockShortcutItemParser.parseShortcutItemsStub = { items in - XCTAssertEqual(items as? [[String: String]], [rawItem]) - parseShortcutItemsExpectation.fulfill() - return [item] - } + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) - let resultExpectation = expectation(description: "result block must be called.") - plugin.handle(call) { result in - XCTAssertNil(result, "result block must be called with nil.") - resultExpectation.fulfill() - } + plugin.setShortcutItems(itemsList: [rawItem]) XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [item], "Must set shortcut items.") - waitForExpectations(timeout: 1) } func testHandleMethodCall_clearShortcutItems() { @@ -58,82 +54,28 @@ class QuickActionsPluginTests: XCTestCase { icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) - let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil) - let mockChannel = MockMethodChannel() - let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() - - mockShortcutItemProvider.shortcutItems = [item] - - let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) - - let resultExpectation = expectation(description: "result block must be called.") - plugin.handle(call) { result in - XCTAssertNil(result, "result block must be called with nil.") - resultExpectation.fulfill() - } - - XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [], "Must clear shortcut items.") - waitForExpectations(timeout: 1) - } - - func testHandleMethodCall_getLaunchAction() { - let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil) - - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) - - let resultExpectation = expectation(description: "result block must be called.") - plugin.handle(call) { result in - XCTAssertNil(result, "result block must be called with nil.") - resultExpectation.fulfill() - } - - waitForExpectations(timeout: 1) - } - - func testHandleMethodCall_nonExistMethods() { - let call = FlutterMethodCall(methodName: "nonExist", arguments: nil) - - let mockChannel = MockMethodChannel() - let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) - let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) + mockShortcutItemProvider.shortcutItems = [item] - let resultExpectation = expectation(description: "result block must be called.") + plugin.clearShortcutItems() - plugin.handle(call) { result in - XCTAssertEqual( - result as? NSObject, FlutterMethodNotImplemented, - "result block must be called with FlutterMethodNotImplemented") - resultExpectation.fulfill() - } + XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [], "Must clear shortcut items.") - waitForExpectations(timeout: 1) } func testApplicationPerformActionForShortcutItem() { - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) let item = UIApplicationShortcutItem( type: "SearchTheThing", @@ -143,30 +85,29 @@ class QuickActionsPluginTests: XCTestCase { userInfo: nil) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") - mockChannel.invokeMethodStub = { method, arguments in - XCTAssertEqual(method, "launch") - XCTAssertEqual(arguments as? String, item.type) + flutterApi.launchActionCallback = { aString in + XCTAssertEqual(aString, item.type) invokeMethodExpectation.fulfill() } let actionResult = plugin.application( UIApplication.shared, performActionFor: item - ) { success in /* no-op */ } + ) { success in + // noop + } XCTAssert(actionResult, "performActionForShortcutItem must return true.") waitForExpectations(timeout: 1) } func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() { - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) let item = UIApplicationShortcutItem( type: "SearchTheThing", @@ -183,14 +124,12 @@ class QuickActionsPluginTests: XCTestCase { } func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() { - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) XCTAssert( @@ -198,18 +137,12 @@ class QuickActionsPluginTests: XCTestCase { } func testApplicationDidBecomeActive_launchWithoutShortcut() { - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) - - mockChannel.invokeMethodStub = { _, _ in - XCTFail("invokeMethod should not be called if launch without shortcut.") - } + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) XCTAssert( @@ -226,19 +159,16 @@ class QuickActionsPluginTests: XCTestCase { icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") - mockChannel.invokeMethodStub = { method, arguments in - XCTAssertEqual(method, "launch") - XCTAssertEqual(arguments as? String, item.type) + flutterApi.launchActionCallback = { aString in + XCTAssertEqual(aString, item.type) invokeMethodExpectation.fulfill() } @@ -261,20 +191,19 @@ class QuickActionsPluginTests: XCTestCase { icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) - let mockChannel = MockMethodChannel() + let flutterApi: MockFlutterApi = MockFlutterApi() let mockShortcutItemProvider = MockShortcutItemProvider() - let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( - channel: mockChannel, - shortcutItemProvider: mockShortcutItemProvider, - shortcutItemParser: mockShortcutItemParser) + flutterApi: flutterApi, + shortcutItemProvider: mockShortcutItemProvider) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") - var invokeMehtodCount = 0 - mockChannel.invokeMethodStub = { method, arguments in - invokeMehtodCount += 1 + var invokeMethodCount = 0 + flutterApi.launchActionCallback = { aString in + XCTAssertEqual(aString, item.type) + invokeMethodCount += 1 invokeMethodExpectation.fulfill() } @@ -288,7 +217,6 @@ class QuickActionsPluginTests: XCTestCase { plugin.applicationDidBecomeActive(UIApplication.shared) waitForExpectations(timeout: 1) - XCTAssertEqual(invokeMehtodCount, 1, "shortcut should only be handled once per launch.") + XCTAssertEqual(invokeMethodCount, 1, "shortcut should only be handled once per launch.") } - } diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift deleted file mode 100644 index 5d52790dd4b2..000000000000 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import Flutter - -/// A channel for platform code to communicate with the Dart code. -protocol MethodChannel { - /// Invokes a method in Dart code. - /// - Parameter method the method name. - /// - Parameter arguments the method arguments. - func invokeMethod(_ method: String, arguments: Any?) -} - -/// A default implementation of the `MethodChannel` protocol. -extension FlutterMethodChannel: MethodChannel {} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift index 8522c5ff5288..dc8ffb812f36 100644 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift @@ -4,48 +4,36 @@ import Flutter -public final class QuickActionsPlugin: NSObject, FlutterPlugin { +public final class QuickActionsPlugin: NSObject, FlutterPlugin, IOSQuickActionsApi { public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel( - name: "plugins.flutter.io/quick_actions_ios", - binaryMessenger: registrar.messenger()) - let instance = QuickActionsPlugin(channel: channel) - registrar.addMethodCallDelegate(instance, channel: channel) + let messenger = registrar.messenger() + let flutterApi = IOSQuickActionsFlutterApi(binaryMessenger: messenger) + let instance = QuickActionsPlugin(flutterApi: flutterApi) + IOSQuickActionsApiSetup.setUp(binaryMessenger: messenger, api: instance) registrar.addApplicationDelegate(instance) } - private let channel: MethodChannel private let shortcutItemProvider: ShortcutItemProviding - private let shortcutItemParser: ShortcutItemParser + private let flutterApi: IOSQuickActionsFlutterApiProtocol /// The type of the shortcut item selected when launching the app. private var launchingShortcutType: String? = nil init( - channel: MethodChannel, - shortcutItemProvider: ShortcutItemProviding = UIApplication.shared, - shortcutItemParser: ShortcutItemParser = DefaultShortcutItemParser() + flutterApi: IOSQuickActionsFlutterApiProtocol, + shortcutItemProvider: ShortcutItemProviding = UIApplication.shared ) { - self.channel = channel + self.flutterApi = flutterApi self.shortcutItemProvider = shortcutItemProvider - self.shortcutItemParser = shortcutItemParser } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "setShortcutItems": - // `arguments` must be an array of dictionaries - let items = call.arguments as! [[String: Any]] - shortcutItemProvider.shortcutItems = shortcutItemParser.parseShortcutItems(items) - result(nil) - case "clearShortcutItems": - shortcutItemProvider.shortcutItems = [] - result(nil) - case "getLaunchAction": - result(nil) - case _: - result(FlutterMethodNotImplemented) - } + func setShortcutItems(itemsList: [ShortcutItemMessage]) { + shortcutItemProvider.shortcutItems = + convertShortcutItemMessageListToUIApplicationShortcutItemList(itemsList) + } + + func clearShortcutItems() { + shortcutItemProvider.shortcutItems = [] } public func application( @@ -84,8 +72,37 @@ public final class QuickActionsPlugin: NSObject, FlutterPlugin { } } - private func handleShortcut(_ shortcut: String) { - channel.invokeMethod("launch", arguments: shortcut) + func handleShortcut(_ shortcut: String) { + flutterApi.launchAction(action: shortcut) { _ in + // noop + } + } + + private func convertShortcutItemMessageListToUIApplicationShortcutItemList( + _ items: [ShortcutItemMessage] + ) -> [UIApplicationShortcutItem] { + return items.compactMap { convertShortcutItemMessageToUIApplicationShortcutItem(with: $0) } } + private func convertShortcutItemMessageToUIApplicationShortcutItem( + with shortcut: ShortcutItemMessage + ) + -> UIApplicationShortcutItem? + { + + let type = shortcut.type + let localizedTitle = shortcut.localizedTitle + + let icon = (shortcut.icon).map { + UIApplicationShortcutIcon(templateImageName: $0) + } + + // type and localizedTitle are required. + return UIApplicationShortcutItem( + type: type, + localizedTitle: localizedTitle, + localizedSubtitle: nil, + icon: icon, + userInfo: nil) + } } diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift deleted file mode 100644 index 0945b4a386f8..000000000000 --- a/packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import UIKit - -/// A parser that parses an array of raw shortcut items. -protocol ShortcutItemParser { - - /// Parses an array of raw shortcut items into an array of UIApplicationShortcutItems - /// - /// - Parameter items an array of raw shortcut items to be parsed. - /// - Returns an array of parsed shortcut items to be set. - /// - func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] -} - -/// A default implementation of the `ShortcutItemParser` protocol. -final class DefaultShortcutItemParser: ShortcutItemParser { - - func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] { - return items.compactMap { deserializeShortcutItem(with: $0) } - } - - private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem? - { - guard - let type = serialized["type"] as? String, - let localizedTitle = serialized["localizedTitle"] as? String - else { - return nil - } - - let icon = (serialized["icon"] as? String).map { - UIApplicationShortcutIcon(templateImageName: $0) - } - - // type and localizedTitle are required. - return UIApplicationShortcutItem( - type: type, - localizedTitle: localizedTitle, - localizedSubtitle: nil, - icon: icon, - userInfo: nil) - } -} diff --git a/packages/quick_actions/quick_actions_ios/ios/Classes/messages.g.swift b/packages/quick_actions/quick_actions_ios/ios/Classes/messages.g.swift new file mode 100644 index 000000000000..0dec7a619942 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/ios/Classes/messages.g.swift @@ -0,0 +1,175 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v12.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#else +#error("Unsupported platform.") +#endif + +extension FlutterError: Error {} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)" + ] +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +/// Home screen quick-action shortcut item. +/// +/// Generated class from Pigeon that represents data sent in messages. +struct ShortcutItemMessage { + /// The identifier of this item; should be unique within the app. + var type: String + /// Localized title of the item. + var localizedTitle: String + /// Name of native resource to be displayed as the icon for this item. + var icon: String? = nil + + static func fromList(_ list: [Any?]) -> ShortcutItemMessage? { + let type = list[0] as! String + let localizedTitle = list[1] as! String + let icon: String? = nilOrValue(list[2]) + + return ShortcutItemMessage( + type: type, + localizedTitle: localizedTitle, + icon: icon + ) + } + func toList() -> [Any?] { + return [ + type, + localizedTitle, + icon, + ] + } +} +private class IOSQuickActionsApiCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return ShortcutItemMessage.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class IOSQuickActionsApiCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? ShortcutItemMessage { + super.writeByte(128) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class IOSQuickActionsApiCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return IOSQuickActionsApiCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return IOSQuickActionsApiCodecWriter(data: data) + } +} + +class IOSQuickActionsApiCodec: FlutterStandardMessageCodec { + static let shared = IOSQuickActionsApiCodec(readerWriter: IOSQuickActionsApiCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol IOSQuickActionsApi { + /// Sets the dynamic shortcuts for the app. + func setShortcutItems(itemsList: [ShortcutItemMessage]) throws + /// Removes all dynamic shortcuts. + func clearShortcutItems() throws +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class IOSQuickActionsApiSetup { + /// The codec used by IOSQuickActionsApi. + static var codec: FlutterStandardMessageCodec { IOSQuickActionsApiCodec.shared } + /// Sets up an instance of `IOSQuickActionsApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: IOSQuickActionsApi?) { + /// Sets the dynamic shortcuts for the app. + let setShortcutItemsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setShortcutItemsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let itemsListArg = args[0] as! [ShortcutItemMessage] + do { + try api.setShortcutItems(itemsList: itemsListArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + setShortcutItemsChannel.setMessageHandler(nil) + } + /// Removes all dynamic shortcuts. + let clearShortcutItemsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + clearShortcutItemsChannel.setMessageHandler { _, reply in + do { + try api.clearShortcutItems() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + clearShortcutItemsChannel.setMessageHandler(nil) + } + } +} +/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. +protocol IOSQuickActionsFlutterApiProtocol { + /// Sends a string representing a shortcut from the native platform to the app. + func launchAction(action actionArg: String, completion: @escaping (Result) -> Void) +} +class IOSQuickActionsFlutterApi: IOSQuickActionsFlutterApiProtocol { + private let binaryMessenger: FlutterBinaryMessenger + init(binaryMessenger: FlutterBinaryMessenger){ + self.binaryMessenger = binaryMessenger + } + /// Sends a string representing a shortcut from the native platform to the app. + func launchAction(action actionArg: String, completion: @escaping (Result) -> Void) { + let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction", binaryMessenger: binaryMessenger) + channel.sendMessage([actionArg] as [Any?]) { _ in + completion(.success(Void())) + } + } +} diff --git a/packages/quick_actions/quick_actions_ios/lib/messages.g.dart b/packages/quick_actions/quick_actions_ios/lib/messages.g.dart new file mode 100644 index 000000000000..f1d644647728 --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/lib/messages.g.dart @@ -0,0 +1,178 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v12.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +/// Home screen quick-action shortcut item. +class ShortcutItemMessage { + ShortcutItemMessage({ + required this.type, + required this.localizedTitle, + this.icon, + }); + + /// The identifier of this item; should be unique within the app. + String type; + + /// Localized title of the item. + String localizedTitle; + + /// Name of native resource to be displayed as the icon for this item. + String? icon; + + Object encode() { + return [ + type, + localizedTitle, + icon, + ]; + } + + static ShortcutItemMessage decode(Object result) { + result as List; + return ShortcutItemMessage( + type: result[0]! as String, + localizedTitle: result[1]! as String, + icon: result[2] as String?, + ); + } +} + +class _IOSQuickActionsApiCodec extends StandardMessageCodec { + const _IOSQuickActionsApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ShortcutItemMessage) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return ShortcutItemMessage.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class IOSQuickActionsApi { + /// Constructor for [IOSQuickActionsApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + IOSQuickActionsApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _IOSQuickActionsApiCodec(); + + /// Sets the dynamic shortcuts for the app. + Future setShortcutItems( + List arg_itemsList) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_itemsList]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + /// Removes all dynamic shortcuts. + Future clearShortcutItems() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} + +abstract class IOSQuickActionsFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + /// Sends a string representing a shortcut from the native platform to the app. + void launchAction(String action); + + static void setup(IOSQuickActionsFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction was null.'); + final List args = (message as List?)!; + final String? arg_action = (args[0] as String?); + assert(arg_action != null, + 'Argument for dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction was null, expected non-null String.'); + try { + api.launchAction(arg_action!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } +} diff --git a/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart b/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart index d19c9ee371bf..235591774c5b 100644 --- a/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart +++ b/packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart @@ -3,54 +3,58 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; +import 'messages.g.dart'; + export 'package:quick_actions_platform_interface/types/types.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/quick_actions_ios'); +late QuickActionHandler _handler; /// An implementation of [QuickActionsPlatform] for iOS. class QuickActionsIos extends QuickActionsPlatform { + /// Creates a new plugin implementation instance. + QuickActionsIos({ + @visibleForTesting IOSQuickActionsApi? api, + }) : _hostApi = api ?? IOSQuickActionsApi(); + + final IOSQuickActionsApi _hostApi; + /// Registers this class as the default instance of [QuickActionsPlatform]. static void registerWith() { QuickActionsPlatform.instance = QuickActionsIos(); } - /// The MethodChannel that is being used by this implementation of the plugin. - @visibleForTesting - MethodChannel get channel => _channel; - @override Future initialize(QuickActionHandler handler) async { - channel.setMethodCallHandler((MethodCall call) async { - assert(call.method == 'launch'); - handler(call.arguments as String); - }); - final String? action = - await channel.invokeMethod('getLaunchAction'); - if (action != null) { - handler(action); - } + final _QuickActionHandlerApi quickActionsHandlerApi = + _QuickActionHandlerApi(); + IOSQuickActionsFlutterApi.setup(quickActionsHandlerApi); + _handler = handler; } @override Future setShortcutItems(List items) async { - final List> itemsList = - items.map(_serializeItem).toList(); - await channel.invokeMethod('setShortcutItems', itemsList); + await _hostApi.setShortcutItems( + items.map(_shortcutItemToShortcutItemMessage).toList(), + ); } @override - Future clearShortcutItems() => - channel.invokeMethod('clearShortcutItems'); - - Map _serializeItem(ShortcutItem item) { - return { - 'type': item.type, - 'localizedTitle': item.localizedTitle, - 'icon': item.icon, - }; + Future clearShortcutItems() => _hostApi.clearShortcutItems(); + + ShortcutItemMessage _shortcutItemToShortcutItemMessage(ShortcutItem item) { + return ShortcutItemMessage( + type: item.type, + localizedTitle: item.localizedTitle, + icon: item.icon, + ); + } +} + +class _QuickActionHandlerApi extends IOSQuickActionsFlutterApi { + @override + void launchAction(String action) { + _handler(action); } } diff --git a/packages/quick_actions/quick_actions_ios/pigeons/copyright.txt b/packages/quick_actions/quick_actions_ios/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/packages/quick_actions/quick_actions_ios/pigeons/messages.dart b/packages/quick_actions/quick_actions_ios/pigeons/messages.dart new file mode 100644 index 000000000000..553d8552cf7f --- /dev/null +++ b/packages/quick_actions/quick_actions_ios/pigeons/messages.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/messages.g.dart', + swiftOut: 'ios/Classes/messages.g.swift', + copyrightHeader: 'pigeons/copyright.txt', +)) + +/// Home screen quick-action shortcut item. +class ShortcutItemMessage { + ShortcutItemMessage( + this.type, + this.localizedTitle, + this.icon, + ); + + /// The identifier of this item; should be unique within the app. + String type; + + /// Localized title of the item. + String localizedTitle; + + /// Name of native resource to be displayed as the icon for this item. + String? icon; +} + +@HostApi() +abstract class IOSQuickActionsApi { + /// Sets the dynamic shortcuts for the app. + void setShortcutItems(List itemsList); + + /// Removes all dynamic shortcuts. + void clearShortcutItems(); +} + +@FlutterApi() +abstract class IOSQuickActionsFlutterApi { + /// Sends a string representing a shortcut from the native platform to the app. + void launchAction(String action); +} diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml index b7e31d8646b2..3be66b4f8866 100644 --- a/packages/quick_actions/quick_actions_ios/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: quick_actions_ios description: An implementation for the iOS platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/packages/tree/main/packages/quick_actions/quick_actions_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 1.0.7 +version: 1.0.8 environment: sdk: ">=2.19.0 <4.0.0" @@ -26,6 +26,7 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter + pigeon: ^12.0.1 plugin_platform_interface: ^2.1.2 topics: diff --git a/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart b/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart index d2b062fff223..c2e281a1a14a 100644 --- a/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart +++ b/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart @@ -2,171 +2,89 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:quick_actions_ios/messages.g.dart'; import 'package:quick_actions_ios/quick_actions_ios.dart'; import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; +const String LAUNCH_ACTION_STRING = 'aString'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$QuickActionsIos', () { - late List log; + final _FakeQuickActionsApi api = _FakeQuickActionsApi(); + final QuickActionsIos quickActions = QuickActionsIos(api: api); - setUp(() { - log = []; - }); - - QuickActionsIos buildQuickActionsPlugin() { - final QuickActionsIos quickActions = QuickActionsIos(); - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler(quickActions.channel, - (MethodCall methodCall) async { - log.add(methodCall); - return ''; - }); - - return quickActions; - } + test('registerWith() registers correct instance', () { + QuickActionsIos.registerWith(); + expect(QuickActionsPlatform.instance, isA()); + }); - test('registerWith() registers correct instance', () { - QuickActionsIos.registerWith(); - expect(QuickActionsPlatform.instance, isA()); + group('#initialize', () { + test('initialize', () { + expect(quickActions.initialize((_) {}), completes); }); + }); - group('#initialize', () { - test('passes getLaunchAction on launch method', () { - final QuickActionsIos quickActions = buildQuickActionsPlugin(); - quickActions.initialize((String type) {}); - - expect( - log, - [ - isMethodCall('getLaunchAction', arguments: null), - ], - ); - }); - - test('initialize', () async { - final QuickActionsIos quickActions = buildQuickActionsPlugin(); - final Completer quickActionsHandler = Completer(); - await quickActions - .initialize((_) => quickActionsHandler.complete(true)); - expect( - log, - [ - isMethodCall('getLaunchAction', arguments: null), - ], - ); - log.clear(); - - expect(quickActionsHandler.future, completion(isTrue)); - }); - }); + test('setShortcutItems', () async { + await quickActions.initialize((String type) {}); + const ShortcutItem item = + ShortcutItem(type: 'test', localizedTitle: 'title', icon: 'icon.svg'); + await quickActions.setShortcutItems([item]); - group('#setShortCutItems', () { - test('passes shortcutItem through channel', () { - final QuickActionsIos quickActions = buildQuickActionsPlugin(); - quickActions.initialize((String type) {}); - quickActions.setShortcutItems([ - const ShortcutItem( - type: 'test', localizedTitle: 'title', icon: 'icon.svg') - ]); - - expect( - log, - [ - isMethodCall('getLaunchAction', arguments: null), - isMethodCall('setShortcutItems', arguments: >[ - { - 'type': 'test', - 'localizedTitle': 'title', - 'icon': 'icon.svg', - } - ]), - ], - ); - }); - - test('setShortcutItems with demo data', () async { - const String type = 'type'; - const String localizedTitle = 'localizedTitle'; - const String icon = 'icon'; - final QuickActionsIos quickActions = buildQuickActionsPlugin(); - await quickActions.setShortcutItems( - const [ - ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon) - ], - ); - expect( - log, - [ - isMethodCall( - 'setShortcutItems', - arguments: >[ - { - 'type': type, - 'localizedTitle': localizedTitle, - 'icon': icon, - } - ], - ), - ], - ); - log.clear(); - }); - }); + expect(api.items.first.type, item.type); + expect(api.items.first.localizedTitle, item.localizedTitle); + expect(api.items.first.icon, item.icon); + }); - group('#clearShortCutItems', () { - test('send clearShortcutItems through channel', () { - final QuickActionsIos quickActions = buildQuickActionsPlugin(); - quickActions.initialize((String type) {}); - quickActions.clearShortcutItems(); - - expect( - log, - [ - isMethodCall('getLaunchAction', arguments: null), - isMethodCall('clearShortcutItems', arguments: null), - ], - ); - }); - - test('clearShortcutItems', () { - final QuickActionsIos quickActions = buildQuickActionsPlugin(); - quickActions.clearShortcutItems(); - expect( - log, - [ - isMethodCall('clearShortcutItems', arguments: null), - ], - ); - log.clear(); - }); - }); + test('clearShortCutItems', () { + quickActions.initialize((String type) {}); + const ShortcutItem item = + ShortcutItem(type: 'test', localizedTitle: 'title', icon: 'icon.svg'); + quickActions.setShortcutItems([item]); + quickActions.clearShortcutItems(); + + expect(api.items.isEmpty, true); }); - group('$ShortcutItem', () { - test('Shortcut item can be constructed', () { - const String type = 'type'; - const String localizedTitle = 'title'; - const String icon = 'foo'; + test('Shortcut item can be constructed', () { + const String type = 'type'; + const String localizedTitle = 'title'; + const String icon = 'foo'; - const ShortcutItem item = - ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon); + const ShortcutItem item = + ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon); - expect(item.type, type); - expect(item.localizedTitle, localizedTitle); - expect(item.icon, icon); - }); + expect(item.type, type); + expect(item.localizedTitle, localizedTitle); + expect(item.icon, icon); }); } -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; +class _FakeQuickActionsApi implements IOSQuickActionsApi { + List items = []; + bool getLaunchActionCalled = false; + + @override + Future clearShortcutItems() async { + items = []; + return; + } + + @override + Future setShortcutItems(List itemsList) async { + await clearShortcutItems(); + for (final ShortcutItemMessage? element in itemsList) { + items.add(shortcutItemMessageToShortcutItem(element!)); + } + } +} + +/// Conversion tool to change [ShortcutItemMessage] back to [ShortcutItem] +ShortcutItem shortcutItemMessageToShortcutItem(ShortcutItemMessage item) { + return ShortcutItem( + type: item.type, + localizedTitle: item.localizedTitle, + icon: item.icon, + ); +}