[quick_actions] convert to pigeon (#5159)

Converts from direct use of method channels to using Pigeon.

Part of https://github.com/flutter/flutter/issues/117844
fixes https://github.com/flutter/flutter/issues/117913
fixes https://github.com/flutter/flutter/issues/117112
This commit is contained in:
Tarrin Neal
2023-10-20 12:28:18 -07:00
committed by GitHub
parent e02d2a5054
commit d1fcbabed0
17 changed files with 614 additions and 512 deletions

View File

@ -1,3 +1,7 @@
## 1.0.8
* Changes method channels to pigeon.
## 1.0.7
* Adds pub topics to package metadata.

View File

@ -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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = "<group>"; };
E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemProvider.swift; sourceTree = "<group>"; };
E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutItemParserTests.swift; sourceTree = "<group>"; };
E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = "<group>"; };
E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemParser.swift; sourceTree = "<group>"; };
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 = "<group>"; };
/* End PBXFileReference section */
@ -120,7 +114,6 @@
E092A7F228D10908005C7F67 /* Mocks */,
33E20B3626EFCDFC00A4A191 /* Info.plist */,
E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */,
E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
@ -215,9 +208,7 @@
E092A7F228D10908005C7F67 /* Mocks */ = {
isa = PBXGroup;
children = (
E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */,
E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */,
E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */,
);
path = Mocks;
sourceTree = "<group>";
@ -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;
};

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,67 +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
import XCTest
@testable import quick_actions_ios
class DefaultShortcutItemParserTests: XCTestCase {
func testParseShortcutItems() {
let rawItem = [
"type": "SearchTheThing",
"localizedTitle": "Search the thing",
"icon": "search_the_thing.png",
]
let expectedItem = UIApplicationShortcutItem(
type: "SearchTheThing",
localizedTitle: "Search the thing",
localizedSubtitle: nil,
icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"),
userInfo: nil)
let parser = DefaultShortcutItemParser()
XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem])
}
func testParseShortcutItems_noIcon() {
let rawItem: [String: Any] = [
"type": "SearchTheThing",
"localizedTitle": "Search the thing",
"icon": NSNull(),
]
let expectedItem = UIApplicationShortcutItem(
type: "SearchTheThing",
localizedTitle: "Search the thing",
localizedSubtitle: nil,
icon: nil,
userInfo: nil)
let parser = DefaultShortcutItemParser()
XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem])
}
func testParseShortcutItems_noType() {
let rawItem = [
"localizedTitle": "Search the thing",
"icon": "search_the_thing.png",
]
let parser = DefaultShortcutItemParser()
XCTAssertEqual(parser.parseShortcutItems([rawItem]), [])
}
func testParseShortcutItems_noLocalizedTitle() {
let rawItem = [
"type": "SearchTheThing",
"icon": "search_the_thing.png",
]
let parser = DefaultShortcutItemParser()
XCTAssertEqual(parser.parseShortcutItems([rawItem]), [])
}
}

View File

@ -1,14 +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 MockMethodChannel: MethodChannel {
var invokeMethodStub: ((_ methods: String, _ arguments: Any?) -> Void)? = nil
func invokeMethod(_ method: String, arguments: Any?) {
invokeMethodStub?(method, arguments)
}
}

View File

@ -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) ?? []
}
}

View File

@ -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, FlutterError>) -> 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)
flutterApi: flutterApi,
shortcutItemProvider: mockShortcutItemProvider)
let parseShortcutItemsExpectation = expectation(
description: "parseShortcutItems must be called.")
mockShortcutItemParser.parseShortcutItemsStub = { items in
XCTAssertEqual(items as? [[String: String]], [rawItem])
parseShortcutItemsExpectation.fulfill()
return [item]
}
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 flutterApi: MockFlutterApi = MockFlutterApi()
let mockShortcutItemProvider = MockShortcutItemProvider()
let mockShortcutItemParser = MockShortcutItemParser()
let plugin = QuickActionsPlugin(
flutterApi: flutterApi,
shortcutItemProvider: mockShortcutItemProvider)
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()
}
plugin.clearShortcutItems()
XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [], "Must clear shortcut items.")
waitForExpectations(timeout: 1)
}
func testHandleMethodCall_getLaunchAction() {
let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil)
let mockChannel = MockMethodChannel()
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()
let plugin = QuickActionsPlugin(
channel: mockChannel,
shortcutItemProvider: mockShortcutItemProvider,
shortcutItemParser: mockShortcutItemParser)
let resultExpectation = expectation(description: "result block must be called.")
plugin.handle(call) { result in
XCTAssertEqual(
result as? NSObject, FlutterMethodNotImplemented,
"result block must be called with FlutterMethodNotImplemented")
resultExpectation.fulfill()
}
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.")
}
}

View File

@ -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 {}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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<T>(_ 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, FlutterError>) -> 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, FlutterError>) -> Void) {
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsFlutterApi.launchAction", binaryMessenger: binaryMessenger)
channel.sendMessage([actionArg] as [Any?]) { _ in
completion(.success(Void()))
}
}
}

View File

@ -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<Object?> wrapResponse(
{Object? result, PlatformException? error, bool empty = false}) {
if (empty) {
return <Object?>[];
}
if (error == null) {
return <Object?>[result];
}
return <Object?>[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 <Object?>[
type,
localizedTitle,
icon,
];
}
static ShortcutItemMessage decode(Object result) {
result as List<Object?>;
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<Object?> codec = _IOSQuickActionsApiCodec();
/// Sets the dynamic shortcuts for the app.
Future<void> setShortcutItems(
List<ShortcutItemMessage?> arg_itemsList) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.setShortcutItems',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_itemsList]) as List<Object?>?;
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<void> clearShortcutItems() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.quick_actions_ios.IOSQuickActionsApi.clearShortcutItems',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
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<Object?> 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<Object?> channel = BasicMessageChannel<Object?>(
'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<Object?> args = (message as List<Object?>?)!;
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()));
}
});
}
}
}
}

View File

@ -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<void> initialize(QuickActionHandler handler) async {
channel.setMethodCallHandler((MethodCall call) async {
assert(call.method == 'launch');
handler(call.arguments as String);
});
final String? action =
await channel.invokeMethod<String?>('getLaunchAction');
if (action != null) {
handler(action);
}
final _QuickActionHandlerApi quickActionsHandlerApi =
_QuickActionHandlerApi();
IOSQuickActionsFlutterApi.setup(quickActionsHandlerApi);
_handler = handler;
}
@override
Future<void> setShortcutItems(List<ShortcutItem> items) async {
final List<Map<String, String?>> itemsList =
items.map(_serializeItem).toList();
await channel.invokeMethod<void>('setShortcutItems', itemsList);
await _hostApi.setShortcutItems(
items.map(_shortcutItemToShortcutItemMessage).toList(),
);
}
@override
Future<void> clearShortcutItems() =>
channel.invokeMethod<void>('clearShortcutItems');
Future<void> clearShortcutItems() => _hostApi.clearShortcutItems();
Map<String, String?> _serializeItem(ShortcutItem item) {
return <String, String?>{
'type': item.type,
'localizedTitle': item.localizedTitle,
'icon': item.icon,
};
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);
}
}

View File

@ -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.

View File

@ -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<ShortcutItemMessage> 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);
}

View File

@ -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:

View File

@ -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<MethodCall> log;
final _FakeQuickActionsApi api = _FakeQuickActionsApi();
final QuickActionsIos quickActions = QuickActionsIos(api: api);
setUp(() {
log = <MethodCall>[];
});
test('registerWith() registers correct instance', () {
QuickActionsIos.registerWith();
expect(QuickActionsPlatform.instance, isA<QuickActionsIos>());
});
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<QuickActionsIos>());
});
group('#initialize', () {
test('passes getLaunchAction on launch method', () {
final QuickActionsIos quickActions = buildQuickActionsPlugin();
quickActions.initialize((String type) {});
expect(
log,
<Matcher>[
isMethodCall('getLaunchAction', arguments: null),
],
);
});
test('initialize', () async {
final QuickActionsIos quickActions = buildQuickActionsPlugin();
final Completer<bool> quickActionsHandler = Completer<bool>();
await quickActions
.initialize((_) => quickActionsHandler.complete(true));
expect(
log,
<Matcher>[
isMethodCall('getLaunchAction', arguments: null),
],
);
log.clear();
expect(quickActionsHandler.future, completion(isTrue));
});
});
group('#setShortCutItems', () {
test('passes shortcutItem through channel', () {
final QuickActionsIos quickActions = buildQuickActionsPlugin();
quickActions.initialize((String type) {});
quickActions.setShortcutItems(<ShortcutItem>[
const ShortcutItem(
type: 'test', localizedTitle: 'title', icon: 'icon.svg')
]);
expect(
log,
<Matcher>[
isMethodCall('getLaunchAction', arguments: null),
isMethodCall('setShortcutItems', arguments: <Map<String, String>>[
<String, String>{
'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>[
ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon)
],
);
expect(
log,
<Matcher>[
isMethodCall(
'setShortcutItems',
arguments: <Map<String, String>>[
<String, String>{
'type': type,
'localizedTitle': localizedTitle,
'icon': icon,
}
],
),
],
);
log.clear();
});
});
group('#clearShortCutItems', () {
test('send clearShortcutItems through channel', () {
final QuickActionsIos quickActions = buildQuickActionsPlugin();
quickActions.initialize((String type) {});
quickActions.clearShortcutItems();
expect(
log,
<Matcher>[
isMethodCall('getLaunchAction', arguments: null),
isMethodCall('clearShortcutItems', arguments: null),
],
);
});
test('clearShortcutItems', () {
final QuickActionsIos quickActions = buildQuickActionsPlugin();
quickActions.clearShortcutItems();
expect(
log,
<Matcher>[
isMethodCall('clearShortcutItems', arguments: null),
],
);
log.clear();
});
group('#initialize', () {
test('initialize', () {
expect(quickActions.initialize((_) {}), completes);
});
});
group('$ShortcutItem', () {
test('Shortcut item can be constructed', () {
const String type = 'type';
const String localizedTitle = 'title';
const String icon = 'foo';
test('setShortcutItems', () async {
await quickActions.initialize((String type) {});
const ShortcutItem item =
ShortcutItem(type: 'test', localizedTitle: 'title', icon: 'icon.svg');
await quickActions.setShortcutItems(<ShortcutItem>[item]);
const ShortcutItem item =
ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon);
expect(api.items.first.type, item.type);
expect(api.items.first.localizedTitle, item.localizedTitle);
expect(api.items.first.icon, item.icon);
});
expect(item.type, type);
expect(item.localizedTitle, localizedTitle);
expect(item.icon, icon);
});
test('clearShortCutItems', () {
quickActions.initialize((String type) {});
const ShortcutItem item =
ShortcutItem(type: 'test', localizedTitle: 'title', icon: 'icon.svg');
quickActions.setShortcutItems(<ShortcutItem>[item]);
quickActions.clearShortcutItems();
expect(api.items.isEmpty, true);
});
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);
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>(T? value) => value;
class _FakeQuickActionsApi implements IOSQuickActionsApi {
List<ShortcutItem> items = <ShortcutItem>[];
bool getLaunchActionCalled = false;
@override
Future<void> clearShortcutItems() async {
items = <ShortcutItem>[];
return;
}
@override
Future<void> setShortcutItems(List<ShortcutItemMessage?> 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,
);
}