diff --git a/assets/imgs/browser.png b/assets/imgs/browser.png new file mode 100644 index 0000000..ee3268e Binary files /dev/null and b/assets/imgs/browser.png differ diff --git a/lib/component/bookmark_edit_dialog.dart b/lib/component/bookmark_edit_dialog.dart new file mode 100644 index 0000000..2132ab0 --- /dev/null +++ b/lib/component/bookmark_edit_dialog.dart @@ -0,0 +1,201 @@ +import 'package:flutter/material.dart'; +import 'package:nostr_sdk/utils/platform_util.dart'; +import 'package:nostr_sdk/utils/string_util.dart'; +import 'package:nowser/data/bookmark.dart'; +import 'package:nowser/data/bookmark_db.dart'; +import 'package:quick_actions/quick_actions.dart'; + +import '../const/base.dart'; +import '../main.dart'; +import '../util/router_util.dart'; +import '../util/table_mode_util.dart'; +import '../util/theme_util.dart'; + +class BookmarkEditDialog extends StatefulWidget { + Bookmark bookmark; + + BookmarkEditDialog(this.bookmark); + + static Future show(BuildContext context, Bookmark bookmark) async { + await showDialog( + context: context, + builder: (_context) { + return BookmarkEditDialog(bookmark); + }, + ); + } + + @override + State createState() { + return _BookmarkEditDialog(); + } +} + +class _BookmarkEditDialog extends State { + TextEditingController nameTextController = TextEditingController(); + + TextEditingController urlTextController = TextEditingController(); + + bool addedToIndex = false; + + bool addedToQa = false; + + @override + void initState() { + super.initState(); + + if (StringUtil.isNotBlank(widget.bookmark.title)) { + nameTextController.text = widget.bookmark.title!; + } + if (StringUtil.isNotBlank(widget.bookmark.url)) { + urlTextController.text = widget.bookmark.url!; + } + } + + @override + Widget build(BuildContext context) { + var themeData = Theme.of(context); + + List list = []; + list.add(Container( + margin: EdgeInsets.only( + bottom: Base.BASE_PADDING, + ), + child: Text( + "Add bookmark", + style: TextStyle( + fontSize: themeData.textTheme.bodyLarge!.fontSize, + fontWeight: FontWeight.bold, + ), + ), + )); + + list.add(Container( + child: TextField( + controller: nameTextController, + decoration: InputDecoration( + labelText: "Name", + ), + ), + )); + + list.add(Container( + child: TextField( + controller: urlTextController, + decoration: InputDecoration( + labelText: "Url", + ), + ), + )); + + list.add(Container( + margin: EdgeInsets.only(top: Base.BASE_PADDING_HALF), + child: Row( + children: [ + Text("Add to index"), + Expanded( + child: Checkbox( + value: addedToIndex, + onChanged: (v) { + if (v != null) { + setState(() { + addedToIndex = v; + }); + } + }, + ), + ) + ], + ), + )); + + list.add(Container( + child: Row( + children: [ + Text("Add to quick action"), + Expanded( + child: Checkbox( + value: addedToQa, + onChanged: (v) { + if (v != null) { + setState(() { + addedToQa = v; + }); + } + }, + ), + ) + ], + ), + )); + + list.add(Container( + margin: EdgeInsets.only( + top: Base.BASE_PADDING * 2, + bottom: Base.BASE_PADDING, + ), + width: double.infinity, + child: FilledButton(onPressed: confirm, child: Text("Confirm")), + )); + + Widget main = Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: list, + ), + ); + if (PlatformUtil.isPC() || TableModeUtil.isTableMode()) { + main = Container( + width: mediaDataCache.size.width / 2, + child: main, + ); + } + + return Dialog( + child: Container( + padding: EdgeInsets.all(Base.BASE_PADDING * 2), + child: main, + ), + ); + } + + Future confirm() async { + var title = nameTextController.text; + var url = urlTextController.text; + + var bookmark = Bookmark( + title: title, + url: url, + id: widget.bookmark.id, + favicon: widget.bookmark.favicon, + weight: widget.bookmark.weight, + createdAt: widget.bookmark.createdAt, + addedToIndex: addedToIndex ? 1 : -1, + addedToQa: addedToQa ? 1 : -1, + ); + + if (bookmark.id == null) { + await BookmarkDB.insert(bookmark); + } else { + await BookmarkDB.update(bookmark); + } + + try { + var allQas = await BookmarkDB.allQas(); + List qas = []; + for (var bk in allQas) { + if (StringUtil.isBlank(bk.title) || StringUtil.isBlank(bk.url)) { + continue; + } + + qas.add(ShortcutItem( + type: bk.url!, localizedTitle: bk.title!, icon: 'ic_launcher')); + quickActions.setShortcutItems(qas); + } + } catch (e) { + print(e); + } + + RouterUtil.back(context); + } +} diff --git a/lib/data/bookmark.dart b/lib/data/bookmark.dart index a1546cb..e84359b 100644 --- a/lib/data/bookmark.dart +++ b/lib/data/bookmark.dart @@ -5,6 +5,7 @@ class Bookmark { String? favicon; int? weight; int? addedToIndex; + int? addedToQa; int? createdAt; Bookmark( @@ -14,6 +15,7 @@ class Bookmark { this.favicon, this.weight, this.addedToIndex, + this.addedToQa, this.createdAt}); Bookmark.fromJson(Map json) { @@ -23,6 +25,7 @@ class Bookmark { favicon = json['favicon']; weight = json['weight']; addedToIndex = json['added_to_index']; + addedToQa = json['added_to_qa']; createdAt = json['created_at']; } @@ -34,6 +37,7 @@ class Bookmark { data['favicon'] = this.favicon; data['weight'] = this.weight; data['added_to_index'] = this.addedToIndex; + data['added_to_qa'] = this.addedToQa; data['created_at'] = this.createdAt; return data; } diff --git a/lib/data/bookmark_db.dart b/lib/data/bookmark_db.dart index d5df4fd..eb3a913 100644 --- a/lib/data/bookmark_db.dart +++ b/lib/data/bookmark_db.dart @@ -28,6 +28,20 @@ class BookmarkDB { return objs; } + static Future> allQas({DatabaseExecutor? db}) async { + List objs = []; + List? arguments = []; + db = await DB.getDB(db); + var sql = + "select * from bookmark where added_to_qa = 1 order by created_at desc"; + List> list = await db.rawQuery(sql, arguments); + for (var i = 0; i < list.length; i++) { + var json = list[i]; + objs.add(Bookmark.fromJson(json)); + } + return objs; + } + static Future deleteByIds(List ids, {DatabaseExecutor? db}) async { var sql = "delete from bookmark where id in("; for (var id in ids) { @@ -39,4 +53,9 @@ class BookmarkDB { db = await DB.getDB(db); await db.execute(sql, ids); } + + static Future update(Bookmark o, {DatabaseExecutor? db}) async { + db = await DB.getDB(db); + await db.update("bookmark", o.toJson(), where: "id = ?", whereArgs: [o.id]); + } } diff --git a/lib/data/db.dart b/lib/data/db.dart index 27b9fe9..557d936 100644 --- a/lib/data/db.dart +++ b/lib/data/db.dart @@ -6,7 +6,7 @@ import 'package:path/path.dart'; import 'package:process_run/shell_run.dart'; class DB { - static const _VERSION = 1; + static const _VERSION = 2; static const _dbName = "nowser.db"; @@ -21,14 +21,14 @@ class DB { } try { - _database = - await openDatabase(path, version: _VERSION, onCreate: _onCreate); + _database = await openDatabase(path, + version: _VERSION, onCreate: _onCreate, onUpgrade: _onUpgrade); } catch (e) { if (Platform.isLinux) { // maybe it need install sqlite first, but this command need run by root. await run('sudo apt-get -y install libsqlite3-0 libsqlite3-dev'); - _database = - await openDatabase(path, version: _VERSION, onCreate: _onCreate); + _database = await openDatabase(path, + version: _VERSION, onCreate: _onCreate, onUpgrade: _onUpgrade); } } } @@ -50,11 +50,19 @@ class DB { db.execute("create index zap_log_index on zap_log (app_id);"); db.execute( - "create table bookmark(id integer not null constraint bookmark_pk primary key autoincrement,title text,url text not null,favicon text,weight integer,added_to_index integer,created_at integer);"); + "create table bookmark(id integer not null constraint bookmark_pk primary key autoincrement,title text,url text not null,favicon text,weight integer,added_to_index integer, added_to_qa integer,created_at integer);"); db.execute( "create table browser_history(id integer not null constraint browser_history_pk primary key autoincrement,title text,url text not null,favicon text,created_at integer);"); } + static Future _onUpgrade( + Database db, int oldVersion, int newVersion) async { + if (oldVersion == 1 && newVersion == 2) { + db.execute( + "alter table bookmark add added_to_qa integer after added_to_index"); + } + } + static Future getCurrentDatabase() async { if (_database == null) { await init(); diff --git a/lib/main.dart b/lib/main.dart index 4325482..f490b31 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,6 +28,7 @@ import 'package:nowser/router/me/me_router.dart'; import 'package:nowser/router/web_tabs_select/web_tabs_select_router.dart'; import 'package:nowser/router/web_url_input/web_url_input_router.dart'; import 'package:provider/provider.dart'; +import 'package:quick_actions/quick_actions.dart'; import 'package:receive_intent/receive_intent.dart' as receiveIntent; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:window_manager/window_manager.dart'; @@ -41,6 +42,7 @@ import 'provider/data_util.dart'; import 'provider/remote_signing_provider.dart'; import 'provider/setting_provider.dart'; import 'util/colors_util.dart'; +import 'util/media_data_cache.dart'; late WebProvider webProvider; @@ -58,6 +60,10 @@ late RootIsolateToken rootIsolateToken; late BuildInRelayProvider buildInRelayProvider; +const QuickActions quickActions = QuickActions(); + +late MediaDataCache mediaDataCache; + Future main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); rootIsolateToken = RootIsolateToken.instance!; diff --git a/lib/provider/android_signer_mixin.dart b/lib/provider/android_signer_mixin.dart index 9d4d9c6..d06ae25 100644 --- a/lib/provider/android_signer_mixin.dart +++ b/lib/provider/android_signer_mixin.dart @@ -38,15 +38,26 @@ mixin AndroidSignerMixin on PermissionCheckMixin { static const String PREFIX = "nostrsigner:"; Future handleInitialIntent(BuildContext context) async { - final intent = await receiveIntent.ReceiveIntent.getInitialIntent(); - if (intent != null) { - // log(intent.toString()); - // log("from ${intent.fromPackageName}"); - // log("action ${intent.action}"); - // log("data ${intent.data}"); - // log("categories ${intent.categories}"); - // log("extra ${intent.extra}"); + var intent = await getInitialIntent(); + await dohandleInitialIntent(context, intent); + } + Future getInitialIntent() async { + var intent = await receiveIntent.ReceiveIntent.getInitialIntent(); + if (intent != null) { + log(intent.toString()); + log("from ${intent.fromPackageName}"); + log("action ${intent.action}"); + log("data ${intent.data}"); + log("categories ${intent.categories}"); + log("extra ${intent.extra}"); + } + return intent; + } + + Future dohandleInitialIntent( + BuildContext context, receiveIntent.Intent? intent) async { + if (intent != null) { if (StringUtil.isNotBlank(intent.data) && intent.data!.startsWith(PREFIX)) { // This is an android signer intent diff --git a/lib/provider/web_provider.dart b/lib/provider/web_provider.dart index 60c5a44..7746f27 100644 --- a/lib/provider/web_provider.dart +++ b/lib/provider/web_provider.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:nostr_sdk/utils/string_util.dart'; +import 'package:nowser/component/bookmark_edit_dialog.dart'; import 'package:nowser/data/bookmark_db.dart'; import 'package:nowser/util/router_util.dart'; @@ -74,12 +75,42 @@ class WebProvider extends ChangeNotifier { } } - void addTab() { - webInfos.add(WebInfo(_rndId(), "")); + void addTab({String url = ""}) { + webInfos.add(WebInfo(_rndId(), url)); index = webInfos.length - 1; notifyListeners(); } + void checkAndOpenUrl(String url) { + if (!url.startsWith("http")) { + return; + } + + int targetIndex = -1; + for (var i = 0; i < webInfos.length; i++) { + var webInfo = webInfos[i]; + if (webInfo.url.contains(url)) { + targetIndex = i; + break; + } + } + + if (targetIndex > -1) { + if (index != targetIndex) { + index = targetIndex; + notifyListeners(); + } + } else { + var _currentWebInfo = currentWebInfo(); + if (_currentWebInfo != null && _currentWebInfo.url == "") { + _currentWebInfo.url = url; + notifyListeners(); + } else { + addTab(url: url); + } + } + } + void changeIndex(WebInfo webInfo) { for (var i = 0; i < webInfos.length; i++) { var owi = webInfos[i]; @@ -146,7 +177,7 @@ class WebProvider extends ChangeNotifier { } catch (e) {} } - void addBookmark(WebInfo webInfo) { + void addBookmark(BuildContext context, WebInfo webInfo) { if (webInfo.browserHistory == null) { return; } @@ -159,7 +190,8 @@ class WebProvider extends ChangeNotifier { bookmark.addedToIndex = -1; bookmark.createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000; - BookmarkDB.insert(bookmark); + // BookmarkDB.insert(bookmark); + BookmarkEditDialog.show(context, bookmark); } void back(BuildContext context) { diff --git a/lib/router/bookmark/bookmark_router.dart b/lib/router/bookmark/bookmark_router.dart index c7227ef..578f55d 100644 --- a/lib/router/bookmark/bookmark_router.dart +++ b/lib/router/bookmark/bookmark_router.dart @@ -1,4 +1,11 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:flutter_pinned_shortcut_plus/flutter_pinned_shortcut_plus.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:nostr_sdk/utils/string_util.dart'; +import 'package:nowser/component/bookmark_edit_dialog.dart'; import 'package:nowser/component/cust_state.dart'; import 'package:nowser/component/deletable_list_mixin.dart'; import 'package:nowser/component/url_list_item_componnet.dart'; @@ -21,6 +28,10 @@ class _BookmarkRouter extends CustState @override Future onReady(BuildContext context) async { + reload(); + } + + Future reload() async { bookmarks = await BookmarkDB.all(); setState(() {}); } @@ -59,6 +70,36 @@ class _BookmarkRouter extends CustState url: bookmark.url ?? "", ); + List slidableActionList = []; + if (Platform.isAndroid) { + slidableActionList.add(SlidableAction( + onPressed: (context) { + doAddPinnedShortcut(bookmark); + }, + backgroundColor: Colors.green, + foregroundColor: Colors.white, + icon: Icons.add_home, + label: 'Desktop', + )); + } + slidableActionList.add(SlidableAction( + onPressed: (context) { + doEdit(bookmark); + }, + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + icon: Icons.edit, + label: 'Edit', + )); + + main = Slidable( + endActionPane: ActionPane( + motion: ScrollMotion(), + children: slidableActionList, + ), + child: main, + ); + main = wrapListItem(main, onTap: () { RouterUtil.back(context, bookmark.url); }, onSelect: () { @@ -86,4 +127,36 @@ class _BookmarkRouter extends CustState selectedIds.clear(); } } + + final _flutterPinnedShortcutPlugin = FlutterPinnedShortcut(); + + Future doAddPinnedShortcut(Bookmark bookmark) async { + File? file; + if (StringUtil.isNotBlank(bookmark.favicon)) { + // use favicon as icon + file = await DefaultCacheManager().getSingleFile(bookmark.favicon!); + } else { + // use default image as icon + // file = + print("favicon not found!"); + return; + } + + if (StringUtil.isBlank(bookmark.title) || + StringUtil.isBlank(bookmark.url)) { + return; + } + + _flutterPinnedShortcutPlugin.createPinnedShortcut( + id: StringUtil.rndNameStr(10), + label: bookmark.title!, + action: bookmark.url!, + iconAssetName: "assets/logo_android.png", + iconUri: Uri.file(file.path).toString()); + } + + Future doEdit(Bookmark bookmark) async { + await BookmarkEditDialog.show(context, bookmark); + reload(); + } } diff --git a/lib/router/index/index_router.dart b/lib/router/index/index_router.dart index a683481..e7133e3 100644 --- a/lib/router/index/index_router.dart +++ b/lib/router/index/index_router.dart @@ -40,15 +40,53 @@ class _IndexRouter extends CustState // start build-in buildInRelayProvider.start(); + + if (PlatformUtil.isAndroid()) { + var intent = await getInitialIntent(); + if (intent != null) { + if (intent.categories != null && + intent.categories!.contains("android.intent.category.LAUNCHER") && + intent.extra != null && + intent.extra!["flutter_pinned_shortcuts"] != null) { + var url = intent.extra!["flutter_pinned_shortcuts"]; + print("find url! $url"); + webProvider.checkAndOpenUrl(url); + } else { + dohandleInitialIntent(context, intent); + } + } + } + + quickActions.initialize((shortcutType) { + print("find quickAction $shortcutType"); + webProvider.checkAndOpenUrl(shortcutType); + }); } @override Widget doBuild(BuildContext context) { - if (PlatformUtil.isAndroid()) { - WidgetsBinding.instance.addPostFrameCallback((_) { - handleInitialIntent(context); - }); - } + // if (PlatformUtil.isAndroid()) { + // WidgetsBinding.instance.addPostFrameCallback((_) async { + // var intent = await getInitialIntent(); + // if (intent != null) { + // if (intent.categories != null && + // intent.categories!.contains("android.intent.category.LAUNCHER") && + // intent.extra != null && + // intent.extra!["flutter_pinned_shortcuts"] != null) { + // var url = intent.extra!["flutter_pinned_shortcuts"]; + // print("find url! $url"); + // webProvider.checkAndOpenUrl(url); + // } else { + // dohandleInitialIntent(context, intent); + // } + // } + + // quickActions.initialize((shortcutType) { + // print("find quickAction $shortcutType"); + // webProvider.checkAndOpenUrl(shortcutType); + // }); + // }); + // } remoteSigningProvider.updateContext(context); webProvider.checkBlank(); diff --git a/lib/router/index/web_control_component.dart b/lib/router/index/web_control_component.dart index c77adb3..d5e90df 100644 --- a/lib/router/index/web_control_component.dart +++ b/lib/router/index/web_control_component.dart @@ -117,7 +117,7 @@ class _WebControlComponent extends State { onTap: () { var webInfo = webProvider.currentWebInfo(); if (webInfo != null) { - webProvider.addBookmark(webInfo); + webProvider.addBookmark(context, webInfo); widget.closeControl(); } }, diff --git a/lib/util/desktop_shutcut_util.dart b/lib/util/desktop_shutcut_util.dart new file mode 100644 index 0000000..ff47b65 --- /dev/null +++ b/lib/util/desktop_shutcut_util.dart @@ -0,0 +1,5 @@ +import 'package:flutter/widgets.dart'; + +class DesktopShutcutUtil { + void create() {} +} diff --git a/lib/util/dio_util.dart b/lib/util/dio_util.dart new file mode 100644 index 0000000..1239cc5 --- /dev/null +++ b/lib/util/dio_util.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; +import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:cookie_jar/cookie_jar.dart'; +import 'dart:convert' as convert; + +Dio? _dio; +var cookieJar = CookieJar(); + +class DioUtil { + static Dio getDio() { + if (_dio == null) { + _dio = Dio(); + if (_dio!.httpClientAdapter is IOHttpClientAdapter) { + (_dio!.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate = + (client) { + client.badCertificateCallback = (cert, host, port) { + return true; + }; + }; + } + + // _dio!.options.connectTimeout = Duration(minutes: 1); + // _dio!.options.receiveTimeout = Duration(minutes: 1); + _dio!.options.headers["user-agent"] = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"; + _dio!.options.headers["accept-encoding"] = "gzip"; + CookieManager cookieManager = CookieManager(cookieJar); + _dio!.interceptors.add(cookieManager); + } + return _dio!; + } + + static setCookie(String link, String key, String value) { + cookieJar.saveFromResponse(Uri.parse(link), [Cookie(key, value)]); + } + + static Future?> get(String link, + [Map? queryParameters, + Map? header]) async { + var dio = getDio(); + if (header != null) { + dio.options.headers.addAll(header); + } + Response resp = await dio.get(link, queryParameters: queryParameters); + if (resp.statusCode == 200) { + if (resp.data is String) { + return json.decode(resp.data); + } + return resp.data; + } else { + return null; + } + } + + static Future getStr(String link, + [Map? queryParameters, + Map? header]) async { + var dio = getDio(); + if (header != null) { + dio.options.headers.addAll(header); + } + Response resp = + await dio.get(link, queryParameters: queryParameters); + if (resp.statusCode == 200) { + return resp.data; + } else { + return null; + } + } + + static Future?> getBytes(String link, + [Map? queryParameters, + Map? header]) async { + var dio = getDio(); + if (header != null) { + dio.options.headers.addAll(header); + } + Response resp = + await dio.get>(link, queryParameters: queryParameters); + if (resp.statusCode == 200) { + return resp.data; + } else { + return null; + } + } + + static Future> post( + String link, Map parameters, + [Map? header]) async { + var dio = getDio(); + if (header != null) { + dio.options.headers.addAll(header); + } + Response resp = await dio.post(link, data: parameters); + return resp.data; + } +} diff --git a/lib/util/media_data_cache.dart b/lib/util/media_data_cache.dart new file mode 100644 index 0000000..3ad5478 --- /dev/null +++ b/lib/util/media_data_cache.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class MediaDataCache { + late Size size; + + late EdgeInsets padding; + + void update(BuildContext context) { + var mediaData = MediaQuery.of(context); + size = mediaData.size; + padding = mediaData.padding; + } +} diff --git a/lib/util/theme_util.dart b/lib/util/theme_util.dart new file mode 100644 index 0000000..7558211 --- /dev/null +++ b/lib/util/theme_util.dart @@ -0,0 +1,9 @@ +// Add some support to get value from theme data. +import 'package:flutter/material.dart'; + +class ThemeUtil { + static Color getDialogCoverColor(ThemeData themeData) { + return (themeData.textTheme.bodyMedium!.color ?? Colors.black) + .withOpacity(0.2); + } +} diff --git a/pubspec.lock b/pubspec.lock index 09bfe3f..1ea5412 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -271,7 +271,7 @@ packages: source: sdk version: "0.0.0" flutter_cache_manager: - dependency: transitive + dependency: "direct main" description: name: flutter_cache_manager sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" @@ -371,6 +371,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_pinned_shortcut_plus: + dependency: "direct main" + description: + name: flutter_pinned_shortcut_plus + sha256: "03d5e6bf8ca157e2b478e0b38a7bf6020b6646d8050e324fc1001f717b365e7c" + url: "https://pub.dev" + source: hosted + version: "0.0.2" flutter_secure_storage: dependency: "direct main" description: @@ -756,6 +764,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + quick_actions: + dependency: "direct main" + description: + name: quick_actions + sha256: "2c1d9a91f3218b4e987a7e1e95ba0415b7f48a2cb3ffacc027a1e3d3c117223f" + url: "https://pub.dev" + source: hosted + version: "1.0.8" + quick_actions_android: + dependency: transitive + description: + name: quick_actions_android + sha256: "926e50d6f879287b34d21934e6c9457f0d851f554179f2a9e8136c4acd1b7062" + url: "https://pub.dev" + source: hosted + version: "1.0.18" + quick_actions_ios: + dependency: transitive + description: + name: quick_actions_ios + sha256: "402596dea62a1028960b93f7651ec22be0e2a91e4fbf92a1c62d3b95f8ff95a5" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + quick_actions_platform_interface: + dependency: transitive + description: + name: quick_actions_platform_interface + sha256: "1fec7068db5122cd019e9340d3d7be5d36eab099695ef3402c7059ee058329a4" + url: "https://pub.dev" + source: hosted + version: "1.1.0" receive_intent: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 3682515..68e0f3c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,9 @@ dependencies: qr_code_scanner: ^1.0.1 flutter_slidable: ^3.1.1 window_manager: ^0.4.2 + quick_actions: ^1.0.8 + flutter_pinned_shortcut_plus: ^0.0.2 + flutter_cache_manager: ^3.4.1 dev_dependencies: flutter_launcher_icons: ^0.13.1