From a23a284d1a9fa15e77f9e2210ca2dd4133f5eab6 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Thu, 5 Oct 2023 19:51:24 +0800 Subject: [PATCH] new: `picker` & opt. `rm -r` --- .dart_tool/flutter_gen/gen_l10n/l10n.dart | 6 +- .dart_tool/flutter_gen/gen_l10n/l10n_de.dart | 2 +- .dart_tool/flutter_gen/gen_l10n/l10n_en.dart | 2 +- .dart_tool/flutter_gen/gen_l10n/l10n_id.dart | 2 +- .dart_tool/flutter_gen/gen_l10n/l10n_zh.dart | 4 +- ios/Runner.xcodeproj/project.pbxproj | 36 +++--- lib/app.dart | 18 +-- lib/core/extension/context/dialog.dart | 87 ++++++++------ lib/data/res/build_data.dart | 6 +- lib/data/store/setting.dart | 6 +- lib/l10n/app_de.arb | 2 +- lib/l10n/app_en.arb | 2 +- lib/l10n/app_id.arb | 2 +- lib/l10n/app_zh.arb | 2 +- lib/l10n/app_zh_tw.arb | 2 +- lib/view/page/server/tab.dart | 22 ++-- lib/view/page/setting/entry.dart | 10 +- lib/view/page/snippet/list.dart | 9 +- lib/view/page/ssh_term.dart | 13 ++- lib/view/page/storage/local.dart | 30 ++--- lib/view/page/storage/sftp.dart | 19 ++-- lib/view/widget/choice_chip.dart | 29 +++++ lib/view/widget/picker.dart | 52 --------- lib/view/widget/server_func_btns.dart | 10 +- lib/view/widget/tag.dart | 114 ------------------- macos/Runner.xcodeproj/project.pbxproj | 12 +- pubspec.lock | 8 ++ pubspec.yaml | 1 + 28 files changed, 192 insertions(+), 316 deletions(-) create mode 100644 lib/view/widget/choice_chip.dart delete mode 100644 lib/view/widget/picker.dart diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index 1f17f1e7..18f41669 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -1202,11 +1202,11 @@ abstract class S { /// **'Preparing to connect...'** String get sftpDlPrepare; - /// No description provided for @sftpRmrfDirSummary. + /// No description provided for @sftpRmrDirSummary. /// /// In en, this message translates to: - /// **'Use `rm -rf` to delete a folder in SFTP.'** - String get sftpRmrfDirSummary; + /// **'Use `rm -r` to delete a folder in SFTP.'** + String get sftpRmrDirSummary; /// No description provided for @sftpSSHConnected. /// diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart index cb29c9d4..fe77074f 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart @@ -583,7 +583,7 @@ class SDe extends S { String get sftpDlPrepare => 'Verbindung vorbereiten...'; @override - String get sftpRmrfDirSummary => 'Verwenden Sie \"rm -rf\", um das Verzeichnis in SFTP zu löschen.'; + String get sftpRmrDirSummary => 'Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.'; @override String get sftpSSHConnected => 'SFTP Verbunden'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index 9a370388..1de99a32 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -583,7 +583,7 @@ class SEn extends S { String get sftpDlPrepare => 'Preparing to connect...'; @override - String get sftpRmrfDirSummary => 'Use `rm -rf` to delete a folder in SFTP.'; + String get sftpRmrDirSummary => 'Use `rm -r` to delete a folder in SFTP.'; @override String get sftpSSHConnected => 'SFTP Connected'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart index f626e96f..b533a9be 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_id.dart @@ -583,7 +583,7 @@ class SId extends S { String get sftpDlPrepare => 'Bersiap untuk terhubung ...'; @override - String get sftpRmrfDirSummary => 'Gunakan `rm -rf` untuk menghapus dir di SFTP'; + String get sftpRmrDirSummary => 'Gunakan `rm -r` untuk menghapus dir di SFTP'; @override String get sftpSSHConnected => 'Sftp terhubung'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index 1b84a0cb..c5a9bdf3 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -583,7 +583,7 @@ class SZh extends S { String get sftpDlPrepare => '准备连接至服务器...'; @override - String get sftpRmrfDirSummary => '在 SFTP 中使用 `rm -rf` 来删除文件夹'; + String get sftpRmrDirSummary => '在 SFTP 中使用 `rm -r` 来删除文件夹'; @override String get sftpSSHConnected => 'SFTP 已连接...'; @@ -1318,7 +1318,7 @@ class SZhTw extends SZh { String get sftpDlPrepare => '準備連接至服務器...'; @override - String get sftpRmrfDirSummary => '在 SFTP 中使用 `rm -rf` 來刪除文件夾'; + String get sftpRmrDirSummary => '在 SFTP 中使用 `rm -r` 來刪除文件夾'; @override String get sftpSSHConnected => 'SFTP 已連接...'; diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 771e5cc4..9a15bf9b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -586,7 +586,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -596,7 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -720,7 +720,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -730,7 +730,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -748,7 +748,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -758,7 +758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -779,7 +779,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -792,7 +792,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; @@ -818,7 +818,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -831,7 +831,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -854,7 +854,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -867,7 +867,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -890,7 +890,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -902,7 +902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; @@ -931,7 +931,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -943,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; @@ -969,7 +969,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -981,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/lib/app.dart b/lib/app.dart index 0b25ad9a..5ae24152 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -73,22 +73,14 @@ class MyApp extends StatelessWidget { Widget _wrapSystemColor(BuildContext context, Widget child) { return DynamicColorBuilder(builder: (light, dark) { - _setupPrimaryColor(context, light, dark); + if (context.isDark && light != null) { + primaryColor = light.primary; + } else if (!context.isDark && dark != null) { + primaryColor = dark.primary; + } return child; }); } - - void _setupPrimaryColor( - BuildContext context, - ColorScheme? light, - ColorScheme? dark, - ) { - if (context.isDark && light != null) { - primaryColor = light.primary; - } else if (!context.isDark && dark != null) { - primaryColor = dark.primary; - } - } } ThemeData _getAmoledTheme(ThemeData darkTheme) => darkTheme.copyWith( diff --git a/lib/core/extension/context/dialog.dart b/lib/core/extension/context/dialog.dart index 21a8bdd3..aebdab49 100644 --- a/lib/core/extension/context/dialog.dart +++ b/lib/core/extension/context/dialog.dart @@ -1,13 +1,11 @@ +import 'package:choice/choice.dart'; import 'package:flutter/material.dart'; import 'package:toolbox/core/extension/context/common.dart'; import 'package:toolbox/core/extension/context/locale.dart'; -import 'package:toolbox/data/res/provider.dart'; +import 'package:toolbox/view/widget/choice_chip.dart'; -import '../../../data/model/server/snippet.dart'; import '../../../data/res/ui.dart'; import '../../../view/widget/input_field.dart'; -import '../../../view/widget/picker.dart'; -import '../../route.dart'; extension DialogX on BuildContext { Future showRoundDialog({ @@ -53,45 +51,60 @@ extension DialogX on BuildContext { ); } - void showSnippetDialog( - void Function(Snippet s) onSelected, - ) { - if (Pros.snippet.snippets.isEmpty) { - showRoundDialog( - child: Text(l10n.noSavedSnippet), - actions: [ - TextButton( - onPressed: () => pop(), - child: Text(l10n.ok), - ), - TextButton( - onPressed: () { - pop(); - AppRoute.snippetEdit().go(this); - }, - child: Text(l10n.add), - ) - ], - ); - return; - } - - var snippet = Pros.snippet.snippets.first; - showRoundDialog( + Future?> showPickDialog({ + required List items, + required String Function(T) name, + bool multi = true, + }) async { + var vals = []; + final sure = await showRoundDialog( title: Text(l10n.choose), - child: Picker( - items: Pros.snippet.snippets.map((e) => Text(e.name)).toList(), - onSelected: (idx) => snippet = Pros.snippet.snippets[idx], + child: Choice( + onChanged: (value) => vals = value, + multiple: multi, + clearable: true, + builder: (state, _) { + return Wrap( + children: List.generate( + items.length, + (index) { + final item = items[index]; + if (item == null) return UIs.placeholder; + return ChoiceChipX( + label: name(item), + state: state, + value: item, + ); + }, + ), + ); + }, ), actions: [ TextButton( - onPressed: () async { - pop(); - onSelected(snippet); - }, + onPressed: () => pop(true), child: Text(l10n.ok), - ) + ), ], ); + if (sure == true && vals.isNotEmpty) { + return vals; + } + return null; + } + + Future showPickSingleDialog({ + required List items, + required String Function(T) name, + }) async { + final vals = await showPickDialog( + items: items, + name: name, + multi: false, + ); + if (vals != null && vals.isNotEmpty) { + return vals.first; + } + return null; } } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 48e4bf40..c439b6ac 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 577; + static const int build = 578; static const String engine = "3.13.5"; - static const String buildAt = "2023-09-25 18:52:38"; - static const int modifications = 3; + static const String buildAt = "2023-09-25 19:49:19"; + static const int modifications = 4; static const int script = 19; } diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 6848796b..428cbbbd 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -184,10 +184,10 @@ class SettingStore extends PersistentStore { true, ); - /// Whether use `rm -rf` to delete directory on SFTP - late final sftpRmrfDir = StoreProperty( + /// Whether use `rm -r` to delete directory on SFTP + late final sftpRmrDir = StoreProperty( box, - 'sftpRmrfDir', + 'sftpRmrDir', false, ); diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 8b3dad73..6a994ff0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -184,7 +184,7 @@ "serverTabUnkown": "Unbekannter Status", "setting": "Einstellungen", "sftpDlPrepare": "Verbindung vorbereiten...", - "sftpRmrfDirSummary": "Verwenden Sie \"rm -rf\", um das Verzeichnis in SFTP zu löschen.", + "sftpRmrDirSummary": "Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.", "sftpSSHConnected": "SFTP Verbunden", "showDistLogo": "Distributionslogo anzeigen", "snippet": "Snippet", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 613d221d..9c51f30b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -184,7 +184,7 @@ "serverTabUnkown": "Unknown state", "setting": "Settings", "sftpDlPrepare": "Preparing to connect...", - "sftpRmrfDirSummary": "Use `rm -rf` to delete a folder in SFTP.", + "sftpRmrDirSummary": "Use `rm -r` to delete a folder in SFTP.", "sftpSSHConnected": "SFTP Connected", "showDistLogo": "Show distribution logo", "snippet": "Snippet", diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index c6482969..bcbaae67 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -184,7 +184,7 @@ "serverTabUnkown": "Negara yang tidak diketahui", "setting": "Pengaturan", "sftpDlPrepare": "Bersiap untuk terhubung ...", - "sftpRmrfDirSummary": "Gunakan `rm -rf` untuk menghapus dir di SFTP", + "sftpRmrDirSummary": "Gunakan `rm -r` untuk menghapus dir di SFTP", "sftpSSHConnected": "Sftp terhubung", "showDistLogo": "Tampilkan logo distribusi", "snippet": "Snippet", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index bfa937c6..6616d0ba 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -184,7 +184,7 @@ "serverTabUnkown": "未知状态", "setting": "设置", "sftpDlPrepare": "准备连接至服务器...", - "sftpRmrfDirSummary": "在 SFTP 中使用 `rm -rf` 来删除文件夹", + "sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 来删除文件夹", "sftpSSHConnected": "SFTP 已连接...", "showDistLogo": "显示发行版 Logo", "snippet": "代码片段", diff --git a/lib/l10n/app_zh_tw.arb b/lib/l10n/app_zh_tw.arb index 72fb4249..26bb35d9 100644 --- a/lib/l10n/app_zh_tw.arb +++ b/lib/l10n/app_zh_tw.arb @@ -184,7 +184,7 @@ "serverTabUnkown": "未知狀態", "setting": "設置", "sftpDlPrepare": "準備連接至服務器...", - "sftpRmrfDirSummary": "在 SFTP 中使用 `rm -rf` 來刪除文件夾", + "sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 來刪除文件夾", "sftpSSHConnected": "SFTP 已連接...", "showDistLogo": "顯示發行版 Logo", "snippet": "程式片段", diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 489c0ea5..113b9589 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -265,18 +265,24 @@ class _ServerPageState extends State tooltip: 'Suspend', ), IconButton( - onPressed: () => _askFor(func: () => srv.client?.execWithPwd( - ShellFunc.shutdown.exec, - context: context, - ), msg: 'Shutdown ${srv.spi.name}',), + onPressed: () => _askFor( + func: () => srv.client?.execWithPwd( + ShellFunc.shutdown.exec, + context: context, + ), + msg: 'Shutdown ${srv.spi.name}', + ), icon: const Icon(Icons.power_off), tooltip: 'Shutdown', ), IconButton( - onPressed: () => _askFor(func: () => srv.client?.execWithPwd( - ShellFunc.reboot.exec, - context: context, - ), msg: 'Reboot ${srv.spi.name}',), + onPressed: () => _askFor( + func: () => srv.client?.execWithPwd( + ShellFunc.reboot.exec, + context: context, + ), + msg: 'Reboot ${srv.spi.name}', + ), icon: const Icon(Icons.restart_alt), tooltip: 'Reboot', ), diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index f430d1fc..d0ea9fb8 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -234,7 +234,7 @@ class _SettingPageState extends State { // Use hardware keyboard on desktop, so there is no need to set it if (isMobile) _buildKeyboardType(), _buildSSHVirtKeys(), - _buildSftpRmrfDir(), + _buildSftpRmrDir(), ].map((e) => RoundRectCard(e)).toList(), ); } @@ -997,11 +997,11 @@ class _SettingPageState extends State { ); } - Widget _buildSftpRmrfDir() { + Widget _buildSftpRmrDir() { return ListTile( - title: const Text('rm -rf'), - subtitle: Text(l10n.sftpRmrfDirSummary, style: UIs.textGrey), - trailing: StoreSwitch(prop: _setting.sftpRmrfDir), + title: const Text('rm -r'), + subtitle: Text(l10n.sftpRmrDirSummary, style: UIs.textGrey), + trailing: StoreSwitch(prop: _setting.sftpRmrDir), ); } diff --git a/lib/view/page/snippet/list.dart b/lib/view/page/snippet/list.dart index 8a827fae..d2af4b61 100644 --- a/lib/view/page/snippet/list.dart +++ b/lib/view/page/snippet/list.dart @@ -127,13 +127,10 @@ class _SnippetListPageState extends State { } Future _runSnippet(Snippet snippet) async { - final servers = await showDialog>( - context: context, - builder: (_) => TagPicker( + final servers = await context.showPickDialog( items: Pros.server.servers.toList(), - tags: Pros.server.tags.toSet(), - ), - ); + name: (e) => e.spi.name, + ); if (servers == null) { return; } diff --git a/lib/view/page/ssh_term.dart b/lib/view/page/ssh_term.dart index 102a90c7..a0e62bdb 100644 --- a/lib/view/page/ssh_term.dart +++ b/lib/view/page/ssh_term.dart @@ -11,7 +11,9 @@ import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/context/snackbar.dart'; import 'package:toolbox/core/utils/platform/base.dart'; +import 'package:toolbox/data/model/server/snippet.dart'; import 'package:toolbox/data/provider/virtual_keyboard.dart'; +import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/data/res/store.dart'; import 'package:xterm/core.dart'; import 'package:xterm/ui.dart' hide TerminalThemes; @@ -253,10 +255,13 @@ class _SSHPageState extends State { } break; case VirtualKeyFunc.snippet: - context.showSnippetDialog((s) { - _terminal.textInput(s.script); - _terminal.keyInput(TerminalKey.enter); - }); + final s = await context.showPickSingleDialog( + items: Pros.snippet.snippets, + name: (p0) => p0.name, + ); + if (s == null) return; + _terminal.textInput(s.script); + _terminal.keyInput(TerminalKey.enter); break; case VirtualKeyFunc.file: // get $PWD from SSH session diff --git a/lib/view/page/storage/local.dart b/lib/view/page/storage/local.dart index a560806f..b2413a8c 100644 --- a/lib/view/page/storage/local.dart +++ b/lib/view/page/storage/local.dart @@ -5,12 +5,12 @@ import 'package:toolbox/core/extension/context/common.dart'; import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/context/snackbar.dart'; +import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:toolbox/data/model/sftp/req.dart'; import 'package:toolbox/data/res/misc.dart'; import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/view/widget/input_field.dart'; import 'package:toolbox/view/widget/omit_start_text.dart'; -import 'package:toolbox/view/widget/picker.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; import '../../../core/extension/numx.dart'; @@ -273,24 +273,15 @@ class _LocalStoragePageState extends State { title: Text(l10n.upload), onTap: () async { context.pop(); - final ids = Pros.server.serverOrder; - var idx = 0; - await context.showRoundDialog( - title: Text(l10n.server), - child: Picker( - items: ids.map((e) => Text(e)).toList(), - onSelected: (idx_) => idx = idx_, - ), - actions: [ - TextButton( - onPressed: () => context.pop(), child: Text(l10n.ok)), - ], + + final spi = await context.showPickSingleDialog( + items: Pros.server.serverOrder + .map((e) => Pros.server.pick(id: e)?.spi) + .toList(), + name: (e) => e.name, ); - final id = ids[idx]; - final spi = Pros.server.pick(id: id)?.spi; - if (spi == null) { - return; - } + if (spi == null) return; + final remotePath = await AppRoute.sftp( spi: spi, isSelect: true, @@ -298,6 +289,7 @@ class _LocalStoragePageState extends State { if (remotePath == null) { return; } + Pros.sftp.add(SftpReq( spi, '$remotePath/$fileName', @@ -346,7 +338,7 @@ class _LocalStoragePageState extends State { final fileName = file.path.split('/').last; context.showRoundDialog( title: Text(l10n.delete), - child: Text(l10n.sureDelete(fileName)), + child: Text(l10n.askContinue('${l10n.delete}: $fileName')), actions: [ TextButton( onPressed: () => context.pop(), diff --git a/lib/view/page/storage/sftp.dart b/lib/view/page/storage/sftp.dart index 904ca54b..aa6daec2 100644 --- a/lib/view/page/storage/sftp.dart +++ b/lib/view/page/storage/sftp.dart @@ -409,13 +409,16 @@ class _SftpPageState extends State with AfterLayoutMixin { void _delete(SftpName file) { context.pop(); final isDir = file.attr.isDirectory; - final useRmrf = Stores.setting.sftpRmrfDir.fetch(); - final dirText = (isDir && !useRmrf) ? '\n${l10n.dirEmpty}' : ''; - final text = l10n.askContinue( - '${l10n.delete} ${l10n.files}(${file.filename})\n$dirText'); - final child = Text(text); + final useRmr = Stores.setting.sftpRmrDir.fetch(); + final text = () { + if (isDir && !useRmr) { + return l10n.askContinue( + '${l10n.dirEmpty}\n${l10n.delete} ${l10n.files}(${file.filename})'); + } + return l10n.askContinue('${l10n.delete} ${l10n.files}(${file.filename})'); + }(); context.showRoundDialog( - child: child, + child: Text(text), title: Text(l10n.attention), actions: [ TextButton( @@ -428,8 +431,8 @@ class _SftpPageState extends State with AfterLayoutMixin { context.showLoadingDialog(); final remotePath = _getRemotePath(file); try { - if (useRmrf) { - await _client!.run('rm -rf "$remotePath"'); + if (useRmr) { + await _client!.run('rm -r "$remotePath"'); } else if (file.attr.isDirectory) { await _status.client!.rmdir(remotePath); } else { diff --git a/lib/view/widget/choice_chip.dart b/lib/view/widget/choice_chip.dart new file mode 100644 index 00000000..deb95bba --- /dev/null +++ b/lib/view/widget/choice_chip.dart @@ -0,0 +1,29 @@ +import 'package:choice/selection.dart'; +import 'package:flutter/material.dart'; + +class ChoiceChipX extends StatelessWidget { + const ChoiceChipX({ + Key? key, + required this.label, + required this.state, + required this.value, + }) : super(key: key); + + final String label; + final ChoiceController state; + final T value; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(3), + child: ChoiceChip( + label: Text(label), + side: BorderSide.none, + selected: state.selected(value), + selectedColor: Theme.of(context).colorScheme.primary, + onSelected: state.onSelected(value), + ), + ); + } +} diff --git a/lib/view/widget/picker.dart b/lib/view/widget/picker.dart deleted file mode 100644 index 48a1b052..00000000 --- a/lib/view/widget/picker.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; - -class Picker extends StatelessWidget { - final List items; - final void Function(int idx) onSelected; - final double height; - - const Picker({ - super.key, - required this.items, - required this.onSelected, - this.height = 157, - }); - - @override - Widget build(BuildContext context) { - final pad = (height - 37) / 2; - return SizedBox( - height: height, - child: Stack( - children: [ - Positioned( - top: pad, - bottom: pad, - left: 0, - right: 0, - child: Container( - height: 37, - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(7)), - color: Colors.black12, - ), - ), - ), - ListWheelScrollView.useDelegate( - itemExtent: 37, - diameterRatio: 2.7, - controller: FixedExtentScrollController(initialItem: 0), - onSelectedItemChanged: (idx) => onSelected(idx), - physics: const FixedExtentScrollPhysics(), - childDelegate: ListWheelChildBuilderDelegate( - builder: (context, index) => Center( - child: items[index], - ), - childCount: items.length, - ), - ) - ], - ), - ); - } -} diff --git a/lib/view/widget/server_func_btns.dart b/lib/view/widget/server_func_btns.dart index ae40a283..587e9093 100644 --- a/lib/view/widget/server_func_btns.dart +++ b/lib/view/widget/server_func_btns.dart @@ -21,7 +21,6 @@ import '../../data/model/pkg/upgrade_info.dart'; import '../../data/model/server/server_private_info.dart'; import '../../data/model/server/snippet.dart'; import 'popup_menu.dart'; -import 'tag.dart'; class ServerFuncBtnsTopRight extends StatelessWidget { final ServerPrivateInfo spi; @@ -96,13 +95,10 @@ void _onTapMoreBtns( ); break; case ServerTabMenuType.snippet: - final snippets = await showDialog>( - context: context, - builder: (_) => TagPicker( + final snippets = await context.showPickDialog( items: Pros.snippet.snippets, - tags: Pros.server.tags.toSet(), - ), - ); + name: (e) => e.name, + ); if (snippets == null) { return; } diff --git a/lib/view/widget/tag.dart b/lib/view/widget/tag.dart index fd043aa4..6a744a1c 100644 --- a/lib/view/widget/tag.dart +++ b/lib/view/widget/tag.dart @@ -6,7 +6,6 @@ import 'package:toolbox/data/res/ui.dart'; import 'package:toolbox/view/widget/input_field.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; -import '../../data/model/app/tag_pickable.dart'; import '../../data/res/color.dart'; const _kTagBtnHeight = 31.0; @@ -179,119 +178,6 @@ class _TagEditorState extends State { } } -class TagPicker extends StatefulWidget { - final List items; - final Set tags; - - const TagPicker({ - Key? key, - required this.items, - required this.tags, - }) : super(key: key); - - @override - _TagPickerState createState() => _TagPickerState(); -} - -class _TagPickerState extends State> { - late MediaQueryData _media; - final List _selected = []; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _media = MediaQuery.of(context); - } - - @override - Widget build(BuildContext context) { - final children = []; - if (widget.tags.isNotEmpty) { - children.add(Text(l10n.tag)); - children.add(UIs.height13); - children.add(SizedBox( - height: _kTagBtnHeight, - width: _media.size.width * 0.7, - child: _buildTags(), - )); - } - if (widget.items.isNotEmpty) { - children.add(Text(l10n.all)); - children.add(UIs.height13); - children.add(SizedBox( - height: _kTagBtnHeight, - width: _media.size.width * 0.7, - child: _buildItems(), - )); - } - final child = widget.tags.isEmpty && widget.items.isEmpty - ? Text(l10n.noOptions) - : Column(mainAxisSize: MainAxisSize.min, children: children); - return AlertDialog( - title: Text(l10n.choose), - content: child, - actions: [ - TextButton( - onPressed: () => context.pop(_selected), - child: Text(l10n.ok), - ), - ], - ); - } - - Widget _buildTags() { - return ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: widget.tags.length, - itemBuilder: (_, idx) { - final item = widget.tags.elementAt(idx); - final isEnable = - widget.items.where((ele) => ele.containsTag(item)).every( - (ele) => _selected.contains(ele), - ); - return TagBtn( - isEnable: isEnable, - onTap: () { - if (isEnable) { - _selected.removeWhere( - (ele) => ele.containsTag(item), - ); - } else { - _selected.addAll(widget.items.where( - (ele) => ele.containsTag(item), - )); - } - setState(() {}); - }, - content: item, - ); - }, - ); - } - - Widget _buildItems() { - return ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: widget.items.length, - itemBuilder: (context, index) { - final e = widget.items[index]; - return TagBtn( - isEnable: _selected.contains(e), - onTap: () { - if (_selected.contains(e)) { - _selected.remove(e); - } else { - _selected.add(e); - } - setState(() {}); - }, - content: e.tagName, - ); - }, - ); - } -} - class TagSwitcher extends StatelessWidget { final List tags; final double width; diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 17369b90..2e534501 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -476,9 +476,9 @@ baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -491,9 +491,9 @@ baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -506,9 +506,9 @@ baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 577; + CURRENT_PROJECT_VERSION = 578; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.577; + MARKETING_VERSION = 1.0.578; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; diff --git a/pubspec.lock b/pubspec.lock index 48b92f73..82f0c18c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -145,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + choice: + dependency: "direct main" + description: + name: choice + sha256: "176e52882753cd6d3cdf537be3c392bc67f8114bc8bd2b1a625207015c6de856" + url: "https://pub.dev" + source: hosted + version: "2.0.0" circle_chart: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 75a4c686..01979cb8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: git: ref: master url: https://github.com/lollipopkit/watch_connectivity + choice: ^2.0.0 dev_dependencies: flutter_native_splash: ^2.1.6