From a07c2fe4bf63e4bcd4c3b3597aabb20c0d737750 Mon Sep 17 00:00:00 2001 From: ngthailam98 Date: Sat, 21 May 2022 17:57:51 +0700 Subject: [PATCH] [Enhancement#9] Refactor ShortcutArg + Update README.md accordingly --- README.md | 57 ++++--- .../FlutterAppShortcutPlugin.kt | 6 +- .../flutter_app_shortcut/ShortcutArg.kt | 20 ++- example/lib/main.dart | 79 ++++++---- .../SwiftFlutterAppShortcutPlugin.swift | 16 +- lib/short_cut_arg.dart | 141 +++++++++++++----- 6 files changed, 222 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index de485b9..2153542 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,8 @@ A plugin to implement Android native App shortcut and IOS quick actions # Getting Started - ## Where to get it? -Go to the plugin official pub.dev page to install +Go to the plugin official [pub.dev](https://pub.dev/packages/flutter_app_shortcut) page to install
@@ -22,14 +21,27 @@ Review [this](/example/lib/main.dart) file to get full example usages ## Arguments -| Name | Value | Requirements | Description | IOS | Android | -| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -| id | String | Unique | When pushing shortcuts with the same ID, the existing one is updated | Yes | Yes | -| shortLabel | String | Not empty | App short cut label (title) | Yes | Yes | -| longLabel | String | | App short cut label when there is a lot of space, in IOS, it will be a subtitle instead | Yes | Yes | -| iconResourceName | String | | Icon resource name (see below for usage) | Yes | Yes | -| uri | String | |Uri when click on shortcut | No | No | -| enabled | boolean | | If enabled == false, user cannot interact with app shortcut | No | No | +### ShortcutArg +| Name | Type | Requirements | Description | +| ------------- | ------------- | ------------- | ------------- | +| id | String | Unique | When pushing shortcuts with the same ID, the existing one is updated | +| title | String | Not empty | App short cut label (title) | +| iconResourceName | String | | Icon resource name (see below for usage) | +| androidArg | AndroidArg | | Extra arguments for Android shortcuts | +| iosArg | IosArg | | Extra arguments for IOS shortcuts | + +### AndroidArg +| Name | Type | Requirements | Description | +| ------------- | ------------- | ------------- | ------------- | +| longLabel | String | | if there is enough space on the device, longLabel replaces title | +| uri | String | |Uri when click on shortcut | + +
+ +### IosArg +| Name | Type | Requirements | Description | +| ------------- | ------------- | ------------- | ------------- | +| subtitle | String | | Subtitle of the shortcut |
@@ -42,11 +54,12 @@ Add a new shortcut FlutterAppShortcut().push( ShortcutArg( id: 'id_1', - shortLabel: 'Home page', - longLabel: 'Go to Home page', + title: 'Home page', iconResourceName: 'ic_android_black', - uri: 'https://www.google.com', - enabled: true, + androidArg: AndroidArg( + longLabel: 'Go to Home page', + uri: 'https://www.google.com', + ) ); ) ``` @@ -60,9 +73,9 @@ Add a new icon FlutterAppShortcut().push( ShortcutArg( id: 'id_1', - shortLabel: 'Home page', - longLabel: 'Subtitle' // in IOS longLabel serves as subtitle instead - iconResourceName: 'register' + title: 'Home page', + iconResourceName: 'register', + iosArg: IosArg(subtitle: "My subtitle), ); ) ``` @@ -76,11 +89,15 @@ Add a new icon ## Android - Pinned shortcut cannot be removed, only disabled +- Shortcut after disabled cannot be enabled, however, pinned shortcut can +- Cannot return icon name when cal getShortcuts - Cannot set disabled message (will implement in the future) - Cannot use icon from flutter side (will implement in the future) - On click short cut does nothing ## IOS - On IOS, enable and disable shortcuts is not available +- Cannot return icon name when cal getShortcuts +- Does no support disable, enable icons - Cannot use icon from flutter side (will implement in the future) - On click short cut does nothing

@@ -89,13 +106,13 @@ Add a new icon - [x] Reseach + Implement for IOS side ## Android - [ ] Allow add disabled message to disabled shortcuts -- [ ] Implement get shortcuts +- [x] Implement get shortcuts - [ ] Allow icon from flutter side - [ ] Enable destination on click shortcut - [ ] Provide accurate errors ## IOS -- [ ] Allow set subtitle -- [ ] Allow set icon +- [x] Allow set subtitle +- [x] Allow set icon - [ ] Allow icon from flutter side - [ ] Enable destination on click shortcut - [ ] Provide accurate errors diff --git a/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/FlutterAppShortcutPlugin.kt b/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/FlutterAppShortcutPlugin.kt index cf410ab..d5652d9 100644 --- a/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/FlutterAppShortcutPlugin.kt +++ b/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/FlutterAppShortcutPlugin.kt @@ -42,7 +42,7 @@ class FlutterAppShortcutPlugin : FlutterPlugin, MethodCallHandler { "getAllShortcuts" -> { return try { val shortcuts = ShortcutManagerCompat.getDynamicShortcuts(applicationContext!!) - result.success(shortcuts.associate { it.id to ShortcutArg.fromInfoCompat(it) }) + result.success(shortcuts.associate { it.id to ShortcutArg.fromInfoCompat(it).toMap() }) } catch (t: Throwable) { result.error("getAllShortcuts", t.message, args) } @@ -59,8 +59,8 @@ class FlutterAppShortcutPlugin : FlutterPlugin, MethodCallHandler { } "enableShortcuts" -> { return try { - val enabledIds = args?.toList() - ?.map { entry -> entry.first } ?: return result.error("enableShortcut", "Invalid args=$args", args) + val enabledIds = args?.toList()?.map { entry -> entry.first } + ?: return result.error("enableShortcut", "Invalid args=$args", args) val shortcutsDynamic = ShortcutManagerCompat.getShortcuts(applicationContext!!, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC) val shortcutsCached = ShortcutManagerCompat.getShortcuts(applicationContext!!, ShortcutManagerCompat.FLAG_MATCH_CACHED) val shortcutsPinned = ShortcutManagerCompat.getShortcuts(applicationContext!!, ShortcutManagerCompat.FLAG_MATCH_PINNED) diff --git a/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/ShortcutArg.kt b/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/ShortcutArg.kt index 78f1d7f..ed7d21d 100644 --- a/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/ShortcutArg.kt +++ b/android/src/main/kotlin/vn/thailam/flutter_app_shortcut/ShortcutArg.kt @@ -10,6 +10,18 @@ data class ShortcutArg( val iconResourceName: String = "", val uri: String = "", ) { + fun toMap(): Map { + return mapOf( + "id" to id, + "title" to shortLabel, + "iconResourceName" to iconResourceName, + "androidArg" to mapOf( + "longLabel" to longLabel, + "uri" to uri + ) + ) + } + companion object { fun fromInfoCompat(infoCompat: ShortcutInfoCompat): ShortcutArg { return ShortcutArg( @@ -22,13 +34,13 @@ data class ShortcutArg( } fun fromHashMap(map: Map): ShortcutArg { - val shortLabel = map["shortLabel"] as String; + val androidArg = map["androidArg"] as Map return ShortcutArg( id = map["id"] as String, - shortLabel = shortLabel, - longLabel = map["longLabel"] as String, + shortLabel = map["title"] as String, + longLabel = androidArg["longLabel"] as String, iconResourceName = map["iconResourceName"] as String, - uri = map["uri"] as String + uri = androidArg["uri"] as String ) } } diff --git a/example/lib/main.dart b/example/lib/main.dart index c22aaba..a47353c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -61,7 +62,6 @@ class _MyAppState extends State { return Container( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), - color: item.enabled ? Colors.blueAccent : Colors.grey, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: Column( @@ -81,10 +81,11 @@ class _MyAppState extends State { ShortcutArg _randomShortcut() => ShortcutArg( id: getRandomString(5), - shortLabel: getRandomString(10), - iconResourceName: 'register', - uri: 'https://www.google.com', - ); + title: getRandomString(10), + iconResourceName: 'ic_android_black', + androidArg: AndroidArg( + uri: 'https://www.google.com', longLabel: "Very long label"), + iosArg: IosArg(subtitle: 'my subtitle')); Widget _getAllBtn() { return Builder(builder: (context) { @@ -97,9 +98,7 @@ class _MyAppState extends State { text += "id=" + element.id + "|"; } ScaffoldMessenger.of(context) - .showSnackBar( - SnackBar(content: Text(text)) - ); + .showSnackBar(SnackBar(content: Text(text))); }, child: const Text('Get current shortcuts')); }); @@ -157,28 +156,52 @@ class _MyAppState extends State { } Widget _enableBtn() { - return TextButton( - onPressed: () async { - await flutterAppShortcut - .enableShortcuts(_shortcuts.map((e) => e.id).toList()); - setState(() { - _shortcuts = - _shortcuts.map((e) => e.copyWith(enabled: true)).toList(); - }); - }, - child: const Text('Enable shortcut')); + return Builder( + builder: (ctx) => TextButton( + onPressed: () async { + if (_isIos(ctx)) { + return; + } + await flutterAppShortcut + .enableShortcuts(_shortcuts.map((e) => e.id).toList()); + setState(() { + _shortcuts = _shortcuts + .map((e) => e.copyWith( + androidArg: e.androidArg?.copyWith(enabled: true))) + .toList(); + }); + }, + child: const Text('Enable shortcut')), + ); + } + + bool _isIos(BuildContext context) { + if (Platform.isIOS) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Function on available on IOS'))); + return true; + } + + return false; } Widget _disableBtn() { - return TextButton( - onPressed: () async { - await flutterAppShortcut - .disableShortcuts(_shortcuts.map((e) => e.id).toList()); - setState(() { - _shortcuts = - _shortcuts.map((e) => e.copyWith(enabled: false)).toList(); - }); - }, - child: const Text('Disable shortcut')); + return Builder( + builder: (ctx) => TextButton( + onPressed: () async { + if (_isIos(ctx)) { + return; + } + await flutterAppShortcut + .disableShortcuts(_shortcuts.map((e) => e.id).toList()); + setState(() { + _shortcuts = _shortcuts + .map((e) => e.copyWith( + androidArg: e.androidArg?.copyWith(enabled: false))) + .toList(); + }); + }, + child: const Text('Disable shortcut')), + ); } } diff --git a/ios/Classes/SwiftFlutterAppShortcutPlugin.swift b/ios/Classes/SwiftFlutterAppShortcutPlugin.swift index aa10a24..0d77a11 100644 --- a/ios/Classes/SwiftFlutterAppShortcutPlugin.swift +++ b/ios/Classes/SwiftFlutterAppShortcutPlugin.swift @@ -9,9 +9,14 @@ struct ShortcutArg { init(dict: Dictionary) { self.id = dict["id"] as? String ?? "" - self.title = dict["shortLabel"] as? String ?? "" - self.subtitle = dict["longLabel"] as? String ?? "" + self.title = dict["title"] as? String ?? "" self.icon = dict["iconResourceName"] as? String ?? "" + // Set subtitle + var subtitle: String = "" + if let iosArg = dict["iosArg"] as? [String: Any] { + subtitle = iosArg["subtitle"] as? String ?? "" + } + self.subtitle = subtitle } } @@ -78,12 +83,15 @@ public class SwiftFlutterAppShortcutPlugin: NSObject, FlutterPlugin { } private func getAllShortcuts(call: FlutterMethodCall, result: FlutterResult) { - var shortcutItems = UIApplication.shared.shortcutItems ?? [] + let shortcutItems = UIApplication.shared.shortcutItems ?? [] var resultDict = [String: Any]() for (_, item) in shortcutItems.enumerated() { resultDict[item.type] = [ "id": item.type, - "shortLabel": item.localizedTitle, + "title": item.localizedTitle, + "iosArg": [ + "subtitle": item.localizedSubtitle + ] ] } diff --git a/lib/short_cut_arg.dart b/lib/short_cut_arg.dart index 9df052a..69cee15 100644 --- a/lib/short_cut_arg.dart +++ b/lib/short_cut_arg.dart @@ -1,76 +1,141 @@ +const String keyId = "id"; +const String keyTitle = "title"; +const String keyIconResourceName = "iconResourceName"; +const String keyAndroidArg = "androidArg"; +const String keyIosArg = "iosArg"; + class ShortcutArg { /// Unique id to identify shortcuts final String id; /// Label of the shortcut - final String shortLabel; - - /// Label of the shortcut, if there is enough room, {longLabel} is used - /// instead of label - /// Default value is {shortLabel} - final String longLabel; + final String title; /// App short cut leading icon final String iconResourceName; - /// Uri of target destination when click on shortcut - final String uri; + /// Args for Android only + final AndroidArg? androidArg; - /// Tells whether the shortcut is enabled or not - /// Setting this does NOT enable/disable the shortcut - /// please use the FlutterAppShortcut class - final bool enabled; + /// Args for IOS only + final IosArg? iosArg; ShortcutArg({ required this.id, - required this.shortLabel, - this.longLabel = '', + required this.title, this.iconResourceName = '', - this.uri = '', - this.enabled = true, + this.androidArg, + this.iosArg, }) : assert(id.isNotEmpty), - assert(shortLabel.isNotEmpty); + assert(title.isNotEmpty), + assert(androidArg != null || iosArg != null); ShortcutArg copyWith({ String? id, - String? shortLabel, - String? longLabel, + String? title, String? iconResourceName, - String? uri, - bool? enabled, + AndroidArg? androidArg, + IosArg? iosArg, }) => ShortcutArg( id: id ?? this.id, - shortLabel: shortLabel ?? this.shortLabel, - longLabel: longLabel ?? this.longLabel, + title: title ?? this.title, iconResourceName: iconResourceName ?? this.iconResourceName, - uri: uri ?? this.uri, - enabled: enabled ?? this.enabled, + androidArg: androidArg ?? this.androidArg, + iosArg: iosArg ?? this.iosArg, ); Map toMap() { return { - 'id': id, - 'shortLabel': shortLabel, - 'longLabel': longLabel, - 'iconResourceName': iconResourceName, - 'uri': uri, - 'enabled': enabled, + keyId: id, + keyTitle: title, + keyIconResourceName: iconResourceName, + keyAndroidArg: androidArg?.toMap(), + keyIosArg: iosArg?.toMap(), }; } factory ShortcutArg.fromMap(Map map) { return ShortcutArg( - id: map['id'] as String, - shortLabel: map['shortLabel'] as String, - longLabel: (map['longLabel'] is String) ? map['longLabel'] : '', - iconResourceName: (map['iconResourceName'] is String) ? map['iconResourceName'] : '', - uri: (map['uri'] is String) ? map['uri'] : '', - enabled: (map['enabled'] is bool) ? map['enabled'] : true); + id: map['id'] as String, + title: map['title'] as String, + iconResourceName: + (map['iconResourceName'] is String) ? map['iconResourceName'] : '', + androidArg: (map[keyAndroidArg] is Map) + ? AndroidArg.fromMap(map[keyAndroidArg]) + : null, + iosArg: (map[keyIosArg] is Map) + ? IosArg.fromMap(map[keyIosArg]) + : null, + ); } @override String toString() { - return "[ShortcutArg] id=$id, shortLabel=$shortLabel, longLabel=$longLabel, iconResourceName=$iconResourceName, uri=$uri, enabled=$enabled"; + return "[ShortcutArg] id=$id, shortLabel=$title, " + "iconResourceName=$iconResourceName, " + "androidArg=${androidArg?.toMap()}, iosArg=${iosArg?.toMap()}"; + } +} + +class AndroidArg { + /// Label of the shortcut, if there is enough room, {longLabel} is used + /// instead of label + /// Default value is {shortLabel} + final String longLabel; + + /// Uri of target destination when click on shortcut + final String uri; + + AndroidArg({this.longLabel = '', this.uri = ''}); + + AndroidArg copyWith({ + String? longLabel, + String? uri, + bool? enabled, + }) => + AndroidArg( + longLabel: longLabel ?? this.longLabel, + uri: uri ?? this.uri, + ); + + Map toMap() { + return { + "longLabel": longLabel, + "uri": uri, + }; + } + + factory AndroidArg.fromMap(Map map) { + return AndroidArg( + longLabel: map['longLabel'] as String, + uri: map['uri'] as String, + ); + } +} + +class IosArg { + /// Subtitle beneath title + final String subtitle; + + IosArg({this.subtitle = ''}); + + IosArg copyWith({ + String? subtitle, + }) => + IosArg( + subtitle: subtitle ?? this.subtitle, + ); + + Map toMap() { + return { + "subtitle": subtitle, + }; + } + + factory IosArg.fromMap(Map map) { + return IosArg( + subtitle: map['subtitle'] as String, + ); } }