diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4301f413..e4cc9498 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -472,7 +472,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 545; + CURRENT_PROJECT_VERSION = 547; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -480,7 +480,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -604,7 +604,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; - CURRENT_PROJECT_VERSION = 545; + CURRENT_PROJECT_VERSION = 547; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -612,7 +612,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -630,7 +630,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 545; + CURRENT_PROJECT_VERSION = 547; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -638,7 +638,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -659,7 +659,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 545; + CURRENT_PROJECT_VERSION = 547; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -672,7 +672,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; @@ -698,7 +698,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 545; + CURRENT_PROJECT_VERSION = 547; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -711,7 +711,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -734,7 +734,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 545; + CURRENT_PROJECT_VERSION = 547; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -747,7 +747,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/lib/core/extension/context.dart b/lib/core/extension/context.dart index 30ba715f..aea37ffe 100644 --- a/lib/core/extension/context.dart +++ b/lib/core/extension/context.dart @@ -1,4 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:toolbox/view/widget/rebuild.dart'; + +import '../../data/model/server/snippet.dart'; +import '../../data/provider/snippet.dart'; +import '../../data/res/ui.dart'; +import '../../locator.dart'; +import '../../view/widget/input_field.dart'; +import '../../view/widget/picker.dart'; +import '../route.dart'; extension ContextX on BuildContext { void pop([T? result]) { @@ -9,3 +19,124 @@ extension ContextX on BuildContext { bool get isDark => Theme.of(this).brightness == Brightness.dark; } + +extension SnackBarX on BuildContext { + void showSnackBar(String text) => + ScaffoldMessenger.of(this).showSnackBar(SnackBar( + content: Text(text), + behavior: SnackBarBehavior.floating, + )); + + void showSnackBarWithAction( + String content, + String action, + GestureTapCallback onTap, + ) { + ScaffoldMessenger.of(this).showSnackBar(SnackBar( + content: Text(content), + behavior: SnackBarBehavior.floating, + action: SnackBarAction( + label: action, + onPressed: onTap, + ), + )); + } + + void showRestartSnackbar({String? btn, String? msg}) { + showSnackBarWithAction( + msg ?? 'Need restart to take effect', + btn ?? 'Restart', + () => RebuildWidget.restartApp(this), + ); + } +} + +extension DialogX on BuildContext { + Future showRoundDialog({ + Widget? child, + List? actions, + Widget? title, + bool barrierDismiss = true, + }) async { + return await showDialog( + context: this, + barrierDismissible: barrierDismiss, + builder: (_) { + return AlertDialog( + title: title, + content: child, + actions: actions, + actionsPadding: const EdgeInsets.all(17), + ); + }, + ); + } + + void showLoadingDialog({bool barrierDismiss = false}) { + showRoundDialog( + child: centerSizedLoading, + barrierDismiss: barrierDismiss, + ); + } + + Future showPwdDialog( + String? user, + ) async { + if (!mounted) return null; + final s = S.of(this)!; + return await showRoundDialog( + title: Text(user ?? s.pwd), + child: Input( + autoFocus: true, + type: TextInputType.visiblePassword, + obscureText: true, + onSubmitted: (val) => pop(val.trim()), + label: s.pwd, + ), + ); + } + + void showSnippetDialog( + S s, + void Function(Snippet s) onSelected, + ) { + final provider = locator(); + if (provider.snippets.isEmpty) { + showRoundDialog( + child: Text(s.noSavedSnippet), + actions: [ + TextButton( + onPressed: () => pop(), + child: Text(s.ok), + ), + TextButton( + onPressed: () { + pop(); + AppRoute.snippetEdit().go(this); + }, + child: Text(s.add), + ) + ], + ); + return; + } + + var snippet = provider.snippets.first; + showRoundDialog( + title: Text(s.choose), + child: Picker( + items: provider.snippets.map((e) => Text(e.name)).toList(), + onSelected: (idx) => snippet = provider.snippets[idx], + ), + actions: [ + TextButton( + onPressed: () async { + pop(); + onSelected(snippet); + }, + child: Text(s.ok), + ) + ], + ); + } +} diff --git a/lib/core/extension/ssh_client.dart b/lib/core/extension/ssh_client.dart index e6086f2d..866f83ff 100644 --- a/lib/core/extension/ssh_client.dart +++ b/lib/core/extension/ssh_client.dart @@ -3,9 +3,9 @@ import 'dart:async'; import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/extension/uint8list.dart'; -import 'package:toolbox/core/utils/ui.dart'; import '../../data/res/misc.dart'; @@ -72,7 +72,7 @@ extension SSHClientX on SSHClient { if (data.contains('[sudo] password for ')) { final user = pwdRequestWithUserReg.firstMatch(data)?.group(1); if (context == null) return; - final pwd = await showPwdDialog(context, user); + final pwd = await context.showPwdDialog(user); if (pwd == null || pwd.isEmpty) { return; } diff --git a/lib/core/update.dart b/lib/core/update.dart index c0aa49af..ed64fc0a 100644 --- a/lib/core/update.dart +++ b/lib/core/update.dart @@ -57,15 +57,14 @@ Future doUpdate(BuildContext context, {bool force = false}) async { final s = S.of(context); if (s == null) { - showSnackBar(context, const Text('Null l10n')); + context.showSnackBar('Null l10n'); return; } final min = update.build.min.current; if (min != null && min > BuildData.build) { - showRoundDialog( - context: context, + context.showRoundDialog( child: Text(s.updateTipTooLow(newest)), actions: [ TextButton( @@ -77,8 +76,7 @@ Future doUpdate(BuildContext context, {bool force = false}) async { return; } - showSnackBarWithAction( - context, + context.showSnackBarWithAction( '${s.updateTip(newest)} \n${update.changelog.current}', s.update, () => _doUpdate(update, context, s), @@ -97,8 +95,7 @@ Future _doUpdate(AppUpdate update, BuildContext context, S s) async { } else if (isMacOS) { await openUrl(url); } else { - showRoundDialog( - context: context, + context.showRoundDialog( child: Text(s.platformNotSupportUpdate), actions: [ TextButton( diff --git a/lib/core/utils/backup.dart b/lib/core/utils/backup.dart deleted file mode 100644 index 65534a5a..00000000 --- a/lib/core/utils/backup.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; - -import '../../data/model/app/backup.dart'; -import '../../data/res/path.dart'; -import '../../data/store/docker.dart'; -import '../../data/store/private_key.dart'; -import '../../data/store/server.dart'; -import '../../data/store/setting.dart'; -import '../../data/store/snippet.dart'; -import '../../locator.dart'; - -final _server = locator(); -final _snippet = locator(); -final _privateKey = locator(); -final _dockerHosts = locator(); -final _setting = locator(); - -Future get backupPath async => '${await docDir}/srvbox_bak.json'; - -const backupFormatVersion = 1; - -Future backup() async { - final result = _diyEncrtpt( - json.encode( - Backup( - version: backupFormatVersion, - date: DateTime.now().toString().split('.').first, - spis: _server.fetch(), - snippets: _snippet.fetch(), - keys: _privateKey.fetch(), - dockerHosts: _dockerHosts.fetchAll(), - settings: _setting.toJson(), - ), - ), - ); - await File(await backupPath).writeAsString(result); -} - -void restore(Backup backup) { - for (final s in backup.snippets) { - _snippet.put(s); - } - for (final s in backup.spis) { - _server.put(s); - } - for (final s in backup.keys) { - _privateKey.put(s); - } - for (final k in backup.dockerHosts.keys) { - final val = backup.dockerHosts[k]; - if (val != null && val is String && val.isNotEmpty) { - _dockerHosts.put(k, val); - } - } -} - -Future decodeBackup(String raw) async { - return await compute(_decode, raw); -} - -Backup _decode(String raw) { - final decrypted = _diyDecrypt(raw); - return Backup.fromJson(json.decode(decrypted)); -} - -String _diyEncrtpt(String raw) => - json.encode(raw.codeUnits.map((e) => e * 2 + 1).toList(growable: false)); -String _diyDecrypt(String raw) { - final list = json.decode(raw); - final sb = StringBuffer(); - for (final e in list) { - sb.writeCharCode((e - 1) ~/ 2); - } - return sb.toString(); -} diff --git a/lib/core/utils/icloud.dart b/lib/core/utils/icloud.dart index 62ebeeae..d8993cfc 100644 --- a/lib/core/utils/icloud.dart +++ b/lib/core/utils/icloud.dart @@ -100,6 +100,9 @@ class ICloud { static Future?> sync({ required Iterable relativePaths, }) async { + final uploadFiles = []; + final downloadFiles = []; + try { final errs = []; @@ -147,11 +150,13 @@ class ICloud { /// Local is newer than remote, so upload local file if (remoteDate.isBefore(localDate)) { + await delete(relativePath); final err = await upload(relativePath: relativePath); if (err != null) { errs.add(err); } //_logger.info('local newer: $relativePath'); + uploadFiles.add(relativePath); return; } @@ -161,6 +166,7 @@ class ICloud { errs.add(err); } //_logger.info('remote newer: $relativePath'); + downloadFiles.add(relativePath); })); await Future.wait(mission); @@ -170,18 +176,18 @@ class ICloud { _logger.warning('Sync failed: $relativePaths', e, s); return [ICloudErr(type: ICloudErrType.generic, message: '$e')]; } finally { - _logger.info('Sync finished.'); + _logger.info('Sync upload: $uploadFiles, download: $downloadFiles'); } } -} -Future syncApple() async { - if (!isIOS && !isMacOS) return; - final docPath = await docDir; - final dir = Directory(docPath); - final files = await dir.list().toList(); - // filter out non-hive(db) files - files.removeWhere((e) => !e.path.endsWith('.hive')); - final paths = files.map((e) => e.path.replaceFirst('$docPath/', '')); - await ICloud.sync(relativePaths: paths); + static Future syncDb() async { + if (!isIOS && !isMacOS) return; + final docPath = await docDir; + final dir = Directory(docPath); + final files = await dir.list().toList(); + // filter out non-hive(db) files + files.removeWhere((e) => !e.path.endsWith('.hive')); + final paths = files.map((e) => e.path.replaceFirst('$docPath/', '')); + await ICloud.sync(relativePaths: paths); + } } diff --git a/lib/core/utils/misc.dart b/lib/core/utils/misc.dart index 384e63ee..c8876871 100644 --- a/lib/core/utils/misc.dart +++ b/lib/core/utils/misc.dart @@ -9,7 +9,6 @@ import 'package:share_plus/share_plus.dart'; import '../../data/provider/app.dart'; import '../../locator.dart'; -import '../../view/widget/rebuild.dart'; import 'platform.dart'; final _app = locator(); @@ -64,10 +63,7 @@ String? getFileName(String? path) { return path.split('/').last; } -void rebuildAll(BuildContext context) { - RebuildWidget.restartApp(context); -} - +/// Return fmt: 2021-01-01 00:00:00 String getTime(int? unixMill) { return DateTime.fromMillisecondsSinceEpoch((unixMill ?? 0) * 1000) .toString() diff --git a/lib/core/utils/ui.dart b/lib/core/utils/ui.dart index 3a1eb5b3..85e8d7b0 100644 --- a/lib/core/utils/ui.dart +++ b/lib/core/utils/ui.dart @@ -3,125 +3,17 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:toolbox/core/extension/context.dart'; -import 'package:toolbox/data/model/app/tab.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../../data/model/server/snippet.dart'; -import '../../data/provider/snippet.dart'; -import '../../data/res/ui.dart'; -import '../../locator.dart'; -import '../../view/widget/input_field.dart'; -import '../../view/widget/picker.dart'; -import '../persistant_store.dart'; -import '../route.dart'; import 'misc.dart'; import 'platform.dart'; import '../extension/stringx.dart'; import '../extension/uint8list.dart'; -void showSnackBar(BuildContext context, Widget child) => - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: child, - behavior: SnackBarBehavior.floating, - )); - -void showSnackBarWithAction( - BuildContext context, - String content, - String action, - GestureTapCallback onTap, -) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(content), - behavior: SnackBarBehavior.floating, - action: SnackBarAction( - label: action, - onPressed: onTap, - ), - )); -} - -void showRestartSnackbar(BuildContext context, {String? btn, String? msg}) { - showSnackBarWithAction( - context, - msg ?? 'Need restart to take effect', - btn ?? 'Restart', - () => rebuildAll(context), - ); -} - Future openUrl(String url) async { return await launchUrl(url.uri, mode: LaunchMode.externalApplication); } -Future showRoundDialog({ - required BuildContext context, - Widget? child, - List? actions, - Widget? title, - bool barrierDismiss = true, -}) async { - return await showDialog( - context: context, - barrierDismissible: barrierDismiss, - builder: (_) { - return AlertDialog( - title: title, - content: child, - actions: actions, - actionsPadding: const EdgeInsets.all(17), - ); - }, - ); -} - -void showLoadingDialog(BuildContext context, {bool barrierDismiss = false}) { - showRoundDialog( - context: context, - child: centerSizedLoading, - barrierDismiss: barrierDismiss, - ); -} - -Future showPwdDialog( - BuildContext context, - String? user, -) async { - if (!context.mounted) return null; - final s = S.of(context)!; - return await showRoundDialog( - context: context, - title: Text(user ?? s.pwd), - child: Input( - autoFocus: true, - type: TextInputType.visiblePassword, - obscureText: true, - onSubmitted: (val) => context.pop(val.trim()), - label: s.pwd, - ), - ); -} - -Widget buildSwitch( - BuildContext context, - StorePropertyBase prop, { - void Function(bool)? func, -}) { - return ValueListenableBuilder( - valueListenable: prop.listenable(), - builder: (context, bool value, widget) { - return Switch( - value: value, - onChanged: (value) { - if (func != null) func(value); - prop.put(value); - }); - }, - ); -} - void setTransparentNavigationBar(BuildContext context) { if (isAndroid) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); @@ -133,18 +25,6 @@ void setTransparentNavigationBar(BuildContext context) { } } -String tabTitleName(BuildContext context, AppTab tab) { - final s = S.of(context)!; - switch (tab) { - case AppTab.server: - return s.server; - case AppTab.snippet: - return s.convert; - case AppTab.ping: - return 'Ping'; - } -} - Future loadFontFile(String localPath) async { if (localPath.isEmpty) return; final name = getFileName(localPath); @@ -156,53 +36,6 @@ Future loadFontFile(String localPath) async { await fontLoader.load(); } -void showSnippetDialog( - BuildContext context, - S s, - void Function(Snippet s) onSelected, -) { - final provider = locator(); - if (provider.snippets.isEmpty) { - showRoundDialog( - context: context, - child: Text(s.noSavedSnippet), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(s.ok), - ), - TextButton( - onPressed: () { - context.pop(); - AppRoute.snippetEdit().go(context); - }, - child: Text(s.add), - ) - ], - ); - return; - } - - var snippet = provider.snippets.first; - showRoundDialog( - context: context, - title: Text(s.choose), - child: Picker( - items: provider.snippets.map((e) => Text(e.name)).toList(), - onSelected: (idx) => snippet = provider.snippets[idx], - ), - actions: [ - TextButton( - onPressed: () async { - context.pop(); - onSelected(snippet); - }, - child: Text(s.ok), - ) - ], - ); -} - void switchStatusBar({required bool hide}) { if (hide) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky, diff --git a/lib/data/model/app/backup.dart b/lib/data/model/app/backup.dart index 73ac9d40..24ce774f 100644 --- a/lib/data/model/app/backup.dart +++ b/lib/data/model/app/backup.dart @@ -1,6 +1,21 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:toolbox/data/model/server/snippet.dart'; +import 'package:toolbox/data/res/path.dart'; +import 'package:toolbox/data/store/docker.dart'; +import 'package:toolbox/data/store/private_key.dart'; +import 'package:toolbox/data/store/server.dart'; +import 'package:toolbox/data/store/setting.dart'; +import 'package:toolbox/data/store/snippet.dart'; +import 'package:toolbox/locator.dart'; + +const backupFormatVersion = 1; + +final _logger = Logger('Backup'); class Backup { // backup format version @@ -45,4 +60,63 @@ class Backup { 'dockerHosts': dockerHosts, 'settings': settings, }; + + Backup.loadFromStore() + : version = backupFormatVersion, + date = DateTime.now().toString().split('.').first, + spis = _server.fetch(), + snippets = _snippet.fetch(), + keys = _privateKey.fetch(), + dockerHosts = _dockerHosts.fetchAll(), + settings = _setting.toJson(); + + static Future backup() async { + final result = _diyEncrtpt(json.encode(Backup.loadFromStore())); + await File(await backupPath).writeAsString(result); + } + + Future restore() async { + for (final s in snippets) { + _snippet.put(s); + } + for (final s in spis) { + _server.put(s); + } + for (final s in keys) { + _privateKey.put(s); + } + for (final k in dockerHosts.keys) { + final val = dockerHosts[k]; + if (val != null && val is String && val.isNotEmpty) { + _dockerHosts.put(k, val); + } + } + } + + Backup.fromJsonString(String raw) + : this.fromJson(json.decode(_diyDecrypt(raw))); +} + +final _server = locator(); +final _snippet = locator(); +final _privateKey = locator(); +final _dockerHosts = locator(); +final _setting = locator(); + +String _diyEncrtpt(String raw) => json.encode( + raw.codeUnits.map((e) => e * 2 + 1).toList(growable: false), + ); + +String _diyDecrypt(String raw) { + try { + final list = json.decode(raw); + final sb = StringBuffer(); + for (final e in list) { + sb.writeCharCode((e - 1) ~/ 2); + } + return sb.toString(); + } catch (e, trace) { + _logger.warning('Decrypt failed', e, trace); + rethrow; + } } diff --git a/lib/data/model/server/proc.dart b/lib/data/model/server/proc.dart index 78763905..3d1af57e 100644 --- a/lib/data/model/server/proc.dart +++ b/lib/data/model/server/proc.dart @@ -144,7 +144,7 @@ class PsResult { procs.add(Proc.parse(line, map)); } catch (e, trace) { errs.add('$line: $e'); - _logger.warning(trace); + _logger.warning('Parse process failed', e, trace); } } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 0feb333e..093ac741 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 = 545; + static const int build = 547; static const String engine = "3.13.2"; - static const String buildAt = "2023-09-11 23:23:58.257948"; - static const int modifications = 6; + static const String buildAt = "2023-09-12 14:04:07.018274"; + static const int modifications = 4; static const int script = 14; } diff --git a/lib/data/res/path.dart b/lib/data/res/path.dart index 0e940fbf..bbaf0865 100644 --- a/lib/data/res/path.dart +++ b/lib/data/res/path.dart @@ -43,3 +43,5 @@ Future get fontDir async { await dir.create(recursive: true); return _fontDir!; } + +Future get backupPath async => '${await docDir}/srvbox_bak.json'; diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 9f8358f8..c6660d0e 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -10,6 +10,7 @@ class SettingStore extends PersistentStore { Map toJson() => {for (var e in box.keys) e: box.get(e)}; // ------BEGIN------ + // // These settings are not displayed in the settings page // You can edit them in the settings json editor (by long press the settings // item in the drawer of the home page) @@ -46,6 +47,25 @@ class SettingStore extends PersistentStore { 'textFactor', 1.0, ); + + /// Lanch page idx + late final launchPage = StoreProperty( + box, + 'launchPage', + defaultLaunchPageIdx, + ); + + /// Server detail disk ignore path + late final diskIgnorePath = + StoreListProperty(box, 'diskIgnorePath', defaultDiskIgnorePath); + + /// Use double column servers page on Desktop + late final doubleColumnServersPage = StoreProperty( + box, + 'doubleColumnServersPage', + isDesktop, + ); + // ------END------ late final primaryColor = StoreProperty( @@ -60,15 +80,6 @@ class SettingStore extends PersistentStore { defaultUpdateInterval, ); - // Lanch page idx - late final launchPage = StoreProperty( - box, - 'launchPage', - defaultLaunchPageIdx, - ); - - late final termColorIdx = StoreProperty(box, 'termColorIdx', 0); - // Max retry count when connect to server late final maxRetryCount = StoreProperty(box, 'maxRetryCount', 2); @@ -93,10 +104,6 @@ class SettingStore extends PersistentStore { // SSH term font size late final termFontSize = StoreProperty(box, 'termFontSize', 13.0); - // Server detail disk ignore path - late final diskIgnorePath = - StoreListProperty(box, 'diskIgnorePath', defaultDiskIgnorePath); - // Locale late final locale = StoreProperty(box, 'locale', ''); @@ -184,13 +191,6 @@ class SettingStore extends PersistentStore { false, ); - /// Use double column servers page on Desktop - late final doubleColumnServersPage = StoreProperty( - box, - 'doubleColumnServersPage', - isDesktop, - ); - /// Whether use system's primary color as the app's primary color late final useSystemPrimaryColor = StoreProperty( box, diff --git a/lib/main.dart b/lib/main.dart index 265d051b..ed2ef372 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -95,7 +95,7 @@ Future initApp() async { primaryColor = Color(settings.primaryColor.fetch()); // Don't call it via `await`, it will block the main thread. - if (settings.icloudSync.fetch()) syncApple(); + if (settings.icloudSync.fetch()) ICloud.syncDb(); if (isAndroid) { // Only start service when [bgRun] is true. diff --git a/lib/view/page/backup.dart b/lib/view/page/backup.dart index 6ce581eb..1842c950 100644 --- a/lib/view/page/backup.dart +++ b/lib/view/page/backup.dart @@ -1,20 +1,24 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:logging/logging.dart'; import 'package:toolbox/core/extension/context.dart'; -import 'package:toolbox/core/utils/backup.dart'; -import 'package:toolbox/core/utils/icloud.dart'; import 'package:toolbox/core/utils/platform.dart'; +import 'package:toolbox/data/model/app/backup.dart'; +import 'package:toolbox/data/res/path.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; import '../../core/utils/misc.dart'; -import '../../core/utils/ui.dart'; import '../../data/res/ui.dart'; import '../../data/store/setting.dart'; import '../../locator.dart'; import '../widget/custom_appbar.dart'; +import '../widget/store_switch.dart'; + +final _logger = Logger('Backup'); class BackupPage extends StatelessWidget { BackupPage({Key? key}) : super(key: key); @@ -62,7 +66,7 @@ class BackupPage extends StatelessWidget { s.backup, Icons.save, () async { - await backup(); + await Backup.backup(); await shareFiles(context, [await backupPath]); }, ) @@ -103,53 +107,47 @@ class BackupPage extends StatelessWidget { textAlign: TextAlign.center, ), width13, - IconButton( - onPressed: () async { - showLoadingDialog(context); - await syncApple(); - context.pop(); - showRestartSnackbar(context, btn: s.restart, msg: s.icloudSynced); - }, - icon: const Icon(Icons.sync)), - width13, - buildSwitch(context, _setting.icloudSync) + // Hive db only save data into local file after app exit, + // so this button is useless + // IconButton( + // onPressed: () async { + // showLoadingDialog(context); + // await ICloud.syncDb(); + // context.pop(); + // showRestartSnackbar(context, btn: s.restart, msg: s.icloudSynced); + // }, + // icon: const Icon(Icons.sync)), + // width13, + StoreSwitch(prop: _setting.icloudSync) ], ); } Future _onRestore(BuildContext context, S s) async { final path = await pickOneFile(); - if (path == null) { - showSnackBar(context, Text(s.notSelected)); - return; - } + if (path == null) return; + final file = File(path); - if (!file.existsSync()) { - showSnackBar(context, Text(s.fileNotExist(path))); + if (!await file.exists()) { + context.showSnackBar(s.fileNotExist(path)); return; } + final text = await file.readAsString(); - _import(text, context, s); - } - - Future _import(String text, BuildContext context, S s) async { if (text.isEmpty) { - showSnackBar(context, Text(s.fieldMustNotEmpty)); + context.showSnackBar(s.fieldMustNotEmpty); return; } - await _importBackup(text, context, s); - } - Future _importBackup(String raw, BuildContext context, S s) async { try { - final backup = await decodeBackup(raw); + context.showLoadingDialog(); + final backup = await compute(Backup.fromJsonString, text.trim()); if (backupFormatVersion != backup.version) { - showSnackBar(context, Text(s.backupVersionNotMatch)); + context.showSnackBar(s.backupVersionNotMatch); return; } - await showRoundDialog( - context: context, + await context.showRoundDialog( title: Text(s.restore), child: Text(s.restoreSureWithDate(backup.date)), actions: [ @@ -159,17 +157,19 @@ class BackupPage extends StatelessWidget { ), TextButton( onPressed: () async { - restore(backup); + backup.restore(); context.pop(); - showRestartSnackbar(context, btn: s.restart, msg: s.needRestart); + context.showRestartSnackbar(btn: s.restart, msg: s.needRestart); }, child: Text(s.ok), ), ], ); - } catch (e) { - showSnackBar(context, Text(e.toString())); - rethrow; + } catch (e, trace) { + _logger.warning('Import backup failed', e, trace); + context.showSnackBar(e.toString()); + } finally { + context.pop(); } } } diff --git a/lib/view/page/convert.dart b/lib/view/page/convert.dart index 592ac4e1..9ea2c551 100644 --- a/lib/view/page/convert.dart +++ b/lib/view/page/convert.dart @@ -3,10 +3,10 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/data/res/ui.dart'; import 'package:toolbox/view/widget/value_notifier.dart'; -import '../../core/utils/ui.dart'; import '../widget/custom_appbar.dart'; import '../widget/input_field.dart'; import '../widget/popup_menu.dart'; @@ -74,7 +74,7 @@ class _ConvertPageState extends State _textEditingControllerResult.text = doConvert(); FocusScope.of(context).requestFocus(FocusNode()); } catch (e) { - showSnackBar(context, Text('Error: \n$e')); + context.showSnackBar('Error: \n$e'); } }, tooltip: _s.convert, diff --git a/lib/view/page/docker.dart b/lib/view/page/docker.dart index 1c87c2b2..09e983f7 100644 --- a/lib/view/page/docker.dart +++ b/lib/view/page/docker.dart @@ -6,7 +6,6 @@ import 'package:toolbox/core/route.dart'; import 'package:toolbox/data/model/docker/image.dart'; import 'package:toolbox/view/widget/input_field.dart'; -import '../../core/utils/ui.dart'; import '../../data/model/docker/ps.dart'; import '../../data/model/server/server_private_info.dart'; import '../../data/provider/docker.dart'; @@ -60,7 +59,7 @@ class _DockerManagePageState extends State { _docker.init( client, widget.spi.user, - (user) async => await showPwdDialog(context, user), + (user) async => await context.showPwdDialog(user), widget.spi.id, context, ); @@ -76,7 +75,7 @@ class _DockerManagePageState extends State { actions: [ IconButton( onPressed: () async { - showLoadingDialog(context); + context.showLoadingDialog(); await _docker.refresh(); context.pop(); }, @@ -101,8 +100,7 @@ class _DockerManagePageState extends State { final imageCtrl = TextEditingController(); final nameCtrl = TextEditingController(); final argsCtrl = TextEditingController(); - await showRoundDialog( - context: context, + await context.showRoundDialog( title: Text(_s.newContainer), child: Column( mainAxisSize: MainAxisSize.min, @@ -151,8 +149,7 @@ class _DockerManagePageState extends State { } Future _showAddCmdPreview(String cmd) async { - await showRoundDialog( - context: context, + await context.showRoundDialog( title: Text(_s.preview), child: Text(cmd), actions: [ @@ -163,11 +160,11 @@ class _DockerManagePageState extends State { TextButton( onPressed: () async { context.pop(); - showLoadingDialog(context); + context.showLoadingDialog(); final result = await _docker.run(cmd); context.pop(); if (result != null) { - showSnackBar(context, Text(result.message ?? _s.unknownError)); + context.showSnackBar(result.message ?? _s.unknownError); } }, child: Text(_s.run), @@ -261,8 +258,7 @@ class _DockerManagePageState extends State { } void _showImageRmDialog(DockerImage e) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureDelete(e.repo)), actions: [ @@ -277,10 +273,7 @@ class _DockerManagePageState extends State { 'docker rmi ${e.id} -f', ); if (result != null) { - showSnackBar( - context, - Text(result.message ?? _s.unknownError), - ); + context.showSnackBar(result.message ?? _s.unknownError); } }, child: Text(_s.ok, style: textRed), @@ -382,15 +375,14 @@ class _DockerManagePageState extends State { onSelected: (DockerMenuType item) async { switch (item) { case DockerMenuType.rm: - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureDelete(dItem.name)), actions: [ TextButton( onPressed: () async { context.pop(); - showLoadingDialog(context); + context.showLoadingDialog(); await _docker.delete(dItem.containerId); context.pop(); }, @@ -400,17 +392,17 @@ class _DockerManagePageState extends State { ); break; case DockerMenuType.start: - showLoadingDialog(context); + context.showLoadingDialog(); await _docker.start(dItem.containerId); context.pop(); break; case DockerMenuType.stop: - showLoadingDialog(context); + context.showLoadingDialog(); await _docker.stop(dItem.containerId); context.pop(); break; case DockerMenuType.restart: - showLoadingDialog(context); + context.showLoadingDialog(); await _docker.restart(dItem.containerId); context.pop(); break; @@ -484,8 +476,7 @@ class _DockerManagePageState extends State { final id = widget.spi.id; final host = _store.fetch(id) ?? 'unix:///run/user/1000/docker.sock'; final ctrl = TextEditingController(text: host); - await showRoundDialog( - context: context, + await context.showRoundDialog( title: Text(_s.dockerEditHost), child: Input( maxLines: 1, diff --git a/lib/view/page/editor.dart b/lib/view/page/editor.dart index 6b931635..73a09236 100644 --- a/lib/view/page/editor.dart +++ b/lib/view/page/editor.dart @@ -9,7 +9,6 @@ import 'package:flutter_highlight/themes/a11y-light.dart'; import 'package:flutter_highlight/themes/monokai.dart'; import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/utils/misc.dart'; -import 'package:toolbox/core/utils/ui.dart'; import 'package:toolbox/data/res/highlight.dart'; import 'package:toolbox/data/store/setting.dart'; import 'package:toolbox/locator.dart'; @@ -107,7 +106,7 @@ class _EditorPageState extends State { // If path is not null, then it's a file editor // save the text and return true to pop the page if (widget.path != null) { - showLoadingDialog(context); + context.showLoadingDialog(); await File(widget.path!).writeAsString(_controller.text); context.pop(); context.pop(true); diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index bb74dec6..285384ed 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -4,6 +4,7 @@ import 'package:after_layout/after_layout.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:get_it/get_it.dart'; +import 'package:toolbox/core/extension/context.dart'; import '../../core/analysis.dart'; import '../../core/route.dart'; @@ -205,8 +206,7 @@ class _HomePageState extends State children: [ _buildIcon(), TextButton( - onPressed: () => showRoundDialog( - context: context, + onPressed: () => context.showRoundDialog( title: Text(_versionStr), child: const Text(BuildData.buildAt), ), @@ -265,8 +265,7 @@ class _HomePageState extends State } void _showAboutDialog() { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.about), child: _buildAboutContent(), actions: [ @@ -374,8 +373,7 @@ class _HomePageState extends State final newSettings = json.decode(result) as Map; _setting.box.putAll(newSettings); } catch (e) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.error), child: Text('${_s.save}:\n$e'), ); diff --git a/lib/view/page/ping.dart b/lib/view/page/ping.dart index 4cdf30b6..93fa702c 100644 --- a/lib/view/page/ping.dart +++ b/lib/view/page/ping.dart @@ -8,7 +8,6 @@ import 'package:toolbox/core/utils/misc.dart'; import 'package:toolbox/view/widget/value_notifier.dart'; import '../../core/extension/uint8list.dart'; -import '../../core/utils/ui.dart'; import '../../data/model/server/ping_result.dart'; import '../../data/provider/server.dart'; import '../../data/res/color.dart'; @@ -71,8 +70,7 @@ class _PingPageState extends State return FloatingActionButton( heroTag: 'ping', onPressed: () { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.choose), child: Input( autoFocus: true, @@ -95,8 +93,7 @@ class _PingPageState extends State try { await doPing(); } catch (e) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.error), child: Text(e.toString()), actions: [ @@ -173,18 +170,18 @@ class _PingPageState extends State _results.value.clear(); final target = _textEditingController.text.trim(); if (target.isEmpty) { - showSnackBar(context, Text(_s.pingInputIP)); + context.showSnackBar(_s.pingInputIP); return; } if (_serverProvider.servers.isEmpty) { - showSnackBar(context, Text(_s.pingNoServer)); + context.showSnackBar(_s.pingNoServer); return; } /// avoid ping command injection if (!targetReg.hasMatch(target)) { - showSnackBar(context, Text(_s.pingInputIP)); + context.showSnackBar(_s.pingInputIP); return; } diff --git a/lib/view/page/private_key/edit.dart b/lib/view/page/private_key/edit.dart index 5502fd41..7f1f2790 100644 --- a/lib/view/page/private_key/edit.dart +++ b/lib/view/page/private_key/edit.dart @@ -11,7 +11,6 @@ import 'package:toolbox/data/res/misc.dart'; import 'package:toolbox/view/widget/input_field.dart'; import '../../../core/utils/server.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/server/private_key_info.dart'; import '../../../data/provider/private_key.dart'; import '../../../data/res/ui.dart'; @@ -92,8 +91,7 @@ class _PrivateKeyEditPageState extends State { IconButton( tooltip: _s.delete, onPressed: () { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureDelete(widget.pki!.id)), actions: [ @@ -128,7 +126,7 @@ class _PrivateKeyEditPageState extends State { final key = _keyController.text.trim(); final pwd = _pwdController.text; if (name.isEmpty || key.isEmpty) { - showSnackBar(context, Text(_s.fieldMustNotEmpty)); + context.showSnackBar(_s.fieldMustNotEmpty); return; } FocusScope.of(context).unfocus(); @@ -144,7 +142,7 @@ class _PrivateKeyEditPageState extends State { _provider.add(pki); } } catch (e) { - showSnackBar(context, Text(e.toString())); + context.showSnackBar(e.toString()); rethrow; } finally { setState(() { @@ -184,25 +182,22 @@ class _PrivateKeyEditPageState extends State { onPressed: () async { final path = await pickOneFile(); if (path == null) { - showSnackBar(context, Text(_s.fieldMustNotEmpty)); + context.showSnackBar(_s.fieldMustNotEmpty); return; } final file = File(path); if (!file.existsSync()) { - showSnackBar(context, Text(_s.fileNotExist(path))); + context.showSnackBar(_s.fileNotExist(path)); return; } final size = (await file.stat()).size; if (size > privateKeyMaxSize) { - showSnackBar( - context, - Text( - _s.fileTooLarge( - path, - size.convertBytes, - privateKeyMaxSize.convertBytes, - ), + context.showSnackBar( + _s.fileTooLarge( + path, + size.convertBytes, + privateKeyMaxSize.convertBytes, ), ); return; diff --git a/lib/view/page/private_key/list.dart b/lib/view/page/private_key/list.dart index cb5be8d9..f3d75242 100644 --- a/lib/view/page/private_key/list.dart +++ b/lib/view/page/private_key/list.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:provider/provider.dart'; import 'package:toolbox/core/extension/context.dart'; -import 'package:toolbox/core/utils/ui.dart'; import 'package:toolbox/data/store/private_key.dart'; import 'package:toolbox/locator.dart'; @@ -94,8 +93,7 @@ class _PrivateKeyListState extends State id: 'system', key: idRsaFile.readAsStringSync(), ); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.addSystemPrivateKeyTip), actions: [ diff --git a/lib/view/page/process.dart b/lib/view/page/process.dart index 7a3ad170..e8f50006 100644 --- a/lib/view/page/process.dart +++ b/lib/view/page/process.dart @@ -7,7 +7,6 @@ import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/uint8list.dart'; import 'package:toolbox/core/utils/misc.dart'; -import '../../core/utils/ui.dart'; import '../../data/model/app/shell_func.dart'; import '../../data/model/server/proc.dart'; import '../../data/model/server/server_private_info.dart'; @@ -67,7 +66,7 @@ class _ProcessPageState extends State { if (mounted) { final result = await _client?.run(AppShellFuncType.process.exec).string; if (result == null || result.isEmpty) { - showSnackBar(context, Text(_s.noResult)); + context.showSnackBar(_s.noResult); return; } _result = PsResult.parse(result, sort: _procSortMode); @@ -113,8 +112,7 @@ class _ProcessPageState extends State { if (_result.error != null) { actions.add(IconButton( icon: const Icon(Icons.error), - onPressed: () => showRoundDialog( - context: context, + onPressed: () => context.showRoundDialog( title: Text(_s.error), child: SingleChildScrollView(child: Text(_result.error!)), actions: [ @@ -166,8 +164,7 @@ class _ProcessPageState extends State { trailing: _buildItemTrail(proc), onTap: () => _lastFocusId = proc.pid, onLongPress: () { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureStop(proc.pid)), actions: [ diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index 91154c35..19ef30b4 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -4,7 +4,6 @@ import 'package:provider/provider.dart'; import 'package:toolbox/core/extension/context.dart'; import '../../../core/route.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/server/private_key_info.dart'; import '../../../data/model/server/server_private_info.dart'; import '../../../data/provider/private_key.dart'; @@ -114,8 +113,7 @@ class _ServerEditPageState extends State { PreferredSizeWidget _buildAppBar() { final delBtn = IconButton( onPressed: () { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureToDeleteServer(widget.spi!.name)), actions: [ @@ -323,12 +321,11 @@ class _ServerEditPageState extends State { void _onSave() async { if (_ipController.text == '') { - showSnackBar(context, Text(_s.plzEnterHost)); + context.showSnackBar(_s.plzEnterHost); return; } if (_keyIdx.value == null && _passwordController.text == '') { - final cancel = await showRoundDialog( - context: context, + final cancel = await context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureNoPwd), actions: [ @@ -348,7 +345,7 @@ class _ServerEditPageState extends State { } // If [_pubKeyIndex] is -1, it means that the user has not selected if (_keyIdx.value == -1) { - showSnackBar(context, Text(_s.plzSelectKey)); + context.showSnackBar(_s.plzSelectKey); return; } if (_usernameController.text.isEmpty) { diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 82c2ccea..5c51981f 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; +import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/media_queryx.dart'; import 'package:toolbox/core/extension/ssh_client.dart'; import 'package:toolbox/data/model/app/shell_func.dart'; @@ -11,7 +12,6 @@ import 'package:toolbox/data/model/app/shell_func.dart'; import '../../../core/route.dart'; import '../../../core/utils/misc.dart'; import '../../../core/utils/platform.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/app/net_view.dart'; import '../../../data/model/server/disk.dart'; import '../../../data/model/server/server.dart'; @@ -376,8 +376,7 @@ class _ServerPageState extends State } void _showFailReason(ServerStatus ss) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.error), child: SingleChildScrollView( child: Text(ss.failedInfo ?? _s.unknownError), diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index dd3ebd16..4b76f2d1 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -16,7 +16,6 @@ import '../../../core/route.dart'; import '../../../core/utils/misc.dart'; import '../../../core/utils/platform.dart'; import '../../../core/update.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/app/net_view.dart'; import '../../../data/provider/app.dart'; import '../../../data/provider/server.dart'; @@ -32,6 +31,7 @@ import '../../widget/custom_appbar.dart'; import '../../widget/future_widget.dart'; import '../../widget/input_field.dart'; import '../../widget/round_rect_card.dart'; +import '../../widget/store_switch.dart'; import '../../widget/value_notifier.dart'; class SettingPage extends StatefulWidget { @@ -43,7 +43,6 @@ class SettingPage extends StatefulWidget { class _SettingPageState extends State { final _themeKey = GlobalKey>(); - //final _startPageKey = GlobalKey>(); final _updateIntervalKey = GlobalKey>(); final _maxRetryKey = GlobalKey>(); final _localeKey = GlobalKey>(); @@ -59,7 +58,6 @@ class _SettingPageState extends State { late SharedPreferences _sp; final _selectedColorValue = ValueNotifier(0); - final _launchPageIdx = ValueNotifier(0); final _nightMode = ValueNotifier(0); final _maxRetryCount = ValueNotifier(0); final _updateInterval = ValueNotifier(0); @@ -91,7 +89,6 @@ class _SettingPageState extends State { super.initState(); _serverProvider = locator(); _setting = locator(); - _launchPageIdx.value = _setting.launchPage.fetch(); _nightMode.value = _setting.themeMode.fetch(); _updateInterval.value = _setting.serverStatusUpdateInterval.fetch(); _maxRetryCount.value = _setting.maxRetryCount.fetch(); @@ -113,8 +110,7 @@ class _SettingPageState extends State { title: Text(_s.setting), actions: [ IconButton( - onPressed: () => showRoundDialog( - context: context, + onPressed: () => context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureDelete(_s.all)), actions: [ @@ -122,7 +118,7 @@ class _SettingPageState extends State { onPressed: () { _setting.box.deleteAll(_setting.box.keys); context.pop(); - showSnackBar(context, Text(_s.success)); + context.showSnackBar(_s.success); }, child: Text(_s.ok, style: const TextStyle(color: Colors.red)), ), @@ -203,9 +199,9 @@ class _SettingPageState extends State { _buildNetViewType(), _buildUpdateInterval(), _buildMaxRetry(), - _buildDiskIgnorePath(), + //_buildDiskIgnorePath(), _buildDeleteServers(), - if (isDesktop) _buildDoubleColumnServersPage(), + //if (isDesktop) _buildDoubleColumnServersPage(), ].map((e) => RoundRectCard(e)).toList(), ); } @@ -251,7 +247,7 @@ class _SettingPageState extends State { title: Text(_s.autoCheckUpdate), subtitle: Text(display, style: grey), onTap: () => doUpdate(ctx, force: true), - trailing: buildSwitch(context, _setting.autoCheckAppUpdate), + trailing: StoreSwitch(prop: _setting.autoCheckAppUpdate), ); }, ); @@ -289,7 +285,7 @@ class _SettingPageState extends State { _setting.serverStatusUpdateInterval.put(val); _serverProvider.startAutoRefresh(); if (val == 0) { - showSnackBar(context, Text(_s.updateIntervalEqual0)); + context.showSnackBar(_s.updateIntervalEqual0); } }, child: Text( @@ -313,8 +309,7 @@ class _SettingPageState extends State { title: Text(_s.primaryColorSeed), onTap: () async { final ctrl = TextEditingController(text: primaryColor.toHex); - await showRoundDialog( - context: context, + await context.showRoundDialog( title: Text(_s.primaryColorSeed), child: StatefulBuilder(builder: (context, setState) { final children = [ @@ -322,9 +317,8 @@ class _SettingPageState extends State { if (!isIOS) ListTile( title: Text(_s.followSystem), - trailing: buildSwitch( - context, - _setting.useSystemPrimaryColor, + trailing: StoreSwitch( + prop: _setting.useSystemPrimaryColor, func: (_) => setState(() {}), ), ) @@ -361,14 +355,14 @@ class _SettingPageState extends State { void _onSaveColor(String s) { final color = s.hexToColor; if (color == null) { - showSnackBar(context, Text(_s.failed)); + context.showSnackBar(_s.failed); return; } _selectedColorValue.value = color.value; _setting.primaryColor.put(_selectedColorValue.value); primaryColor = color; context.pop(); - showRestartSnackbar(context, btn: _s.restart, msg: _s.needRestart); + context.showRestartSnackbar(btn: _s.restart, msg: _s.needRestart); } // Widget _buildLaunchPage() { @@ -515,9 +509,9 @@ class _SettingPageState extends State { onPressed: () { if (_pushToken.value != null) { copy2Clipboard(_pushToken.value!); - showSnackBar(context, Text(_s.success)); + context.showSnackBar(_s.success); } else { - showSnackBar(context, Text(_s.getPushTokenFailed)); + context.showSnackBar(_s.getPushTokenFailed); } }, ), @@ -548,8 +542,7 @@ class _SettingPageState extends State { style: textSize15, ), onTap: () { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.font), actions: [ TextButton( @@ -560,8 +553,7 @@ class _SettingPageState extends State { onPressed: () { _setting.fontPath.delete(); context.pop(); - showRestartSnackbar( - context, + context.showRestartSnackbar( btn: _s.restart, msg: _s.needRestart, ); @@ -588,16 +580,16 @@ class _SettingPageState extends State { } context.pop(); - showRestartSnackbar(context, btn: _s.restart, msg: _s.needRestart); + context.showRestartSnackbar(btn: _s.restart, msg: _s.needRestart); return; } - showSnackBar(context, Text(_s.failed)); + context.showSnackBar(_s.failed); } Widget _buildBgRun() { return ListTile( title: Text(_s.bgRun), - trailing: buildSwitch(context, _setting.bgRun), + trailing: StoreSwitch(prop: _setting.bgRun), ); } @@ -615,42 +607,42 @@ class _SettingPageState extends State { ); } - Widget _buildDiskIgnorePath() { - final paths = _setting.diskIgnorePath.fetch(); - return ListTile( - title: Text(_s.diskIgnorePath), - trailing: Text(_s.edit, style: textSize15), - onTap: () { - final ctrller = TextEditingController(text: json.encode(paths)); - void onSubmit() { - try { - final list = List.from(json.decode(ctrller.text)); - _setting.diskIgnorePath.put(list); - context.pop(); - showSnackBar(context, Text(_s.success)); - } catch (e) { - showSnackBar(context, Text(e.toString())); - } - } + // Widget _buildDiskIgnorePath() { + // final paths = _setting.diskIgnorePath.fetch(); + // return ListTile( + // title: Text(_s.diskIgnorePath), + // trailing: Text(_s.edit, style: textSize15), + // onTap: () { + // final ctrller = TextEditingController(text: json.encode(paths)); + // void onSubmit() { + // try { + // final list = List.from(json.decode(ctrller.text)); + // _setting.diskIgnorePath.put(list); + // context.pop(); + // showSnackBar(context, Text(_s.success)); + // } catch (e) { + // showSnackBar(context, Text(e.toString())); + // } + // } - showRoundDialog( - context: context, - title: Text(_s.diskIgnorePath), - child: Input( - autoFocus: true, - controller: ctrller, - label: 'JSON', - type: TextInputType.visiblePassword, - maxLines: 3, - onSubmitted: (_) => onSubmit(), - ), - actions: [ - TextButton(onPressed: onSubmit, child: Text(_s.ok)), - ], - ); - }, - ); - } + // showRoundDialog( + // context: context, + // title: Text(_s.diskIgnorePath), + // child: Input( + // autoFocus: true, + // controller: ctrller, + // label: 'JSON', + // type: TextInputType.visiblePassword, + // maxLines: 3, + // onSubmitted: (_) => onSubmit(), + // ), + // actions: [ + // TextButton(onPressed: onSubmit, child: Text(_s.ok)), + // ], + // ); + // }, + // ); + // } Widget _buildLocale() { final items = S.supportedLocales @@ -675,7 +667,7 @@ class _SettingPageState extends State { onSelected: (String idx) { _localeCode.value = idx; _setting.locale.put(idx); - showRestartSnackbar(context, btn: _s.restart, msg: _s.needRestart); + context.showRestartSnackbar(btn: _s.restart, msg: _s.needRestart); }, child: Text( _s.languageName, @@ -690,7 +682,7 @@ class _SettingPageState extends State { return ListTile( title: Text(_s.sshVirtualKeyAutoOff), subtitle: const Text('Ctrl & Alt', style: grey), - trailing: buildSwitch(context, _setting.sshVirtualKeyAutoOff), + trailing: StoreSwitch(prop: _setting.sshVirtualKeyAutoOff), ); } @@ -763,11 +755,9 @@ class _SettingPageState extends State { Widget _buildFullScreenSwitch() { return ListTile( title: Text(_s.fullScreen), - trailing: buildSwitch( - context, - _setting.fullScreen, - func: (_) => showRestartSnackbar( - context, + trailing: StoreSwitch( + prop: _setting.fullScreen, + func: (_) => context.showRestartSnackbar( btn: _s.restart, msg: _s.needRestart, ), @@ -779,7 +769,7 @@ class _SettingPageState extends State { return ListTile( title: Text(_s.fullScreenJitter), subtitle: Text(_s.fullScreenJitterHelp, style: grey), - trailing: buildSwitch(context, _setting.fullScreenJitter), + trailing: StoreSwitch(prop: _setting.fullScreenJitter), ); } @@ -886,9 +876,9 @@ class _SettingPageState extends State { map.forEach((key, value) { _sp.setString(key, value); }); - showSnackBar(context, Text(_s.success)); + context.showSnackBar(_s.success); } catch (e) { - showSnackBar(context, Text(e.toString())); + context.showSnackBar(e.toString()); } } @@ -905,8 +895,7 @@ class _SettingPageState extends State { } }); final ctrl = TextEditingController(text: json.encode(data)); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.homeWidgetUrlConfig), child: Input( autoFocus: true, @@ -964,7 +953,7 @@ class _SettingPageState extends State { return ListTile( title: Text(_s.autoUpdateHomeWidget), subtitle: Text(_s.whenOpenApp, style: grey), - trailing: buildSwitch(context, _setting.autoUpdateHomeWidget), + trailing: StoreSwitch(prop: _setting.autoUpdateHomeWidget), ); } @@ -975,8 +964,7 @@ class _SettingPageState extends State { onTap: () async { final all = locator().box.keys.map( (e) => TextButton( - onPressed: () => showRoundDialog( - context: context, + onPressed: () => context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureDelete(e)), actions: [ @@ -989,8 +977,7 @@ class _SettingPageState extends State { child: Text(e), ), ); - showRoundDialog>( - context: context, + context.showRoundDialog>( title: Text(_s.choose), child: SingleChildScrollView( child: Column( @@ -1007,7 +994,7 @@ class _SettingPageState extends State { return ListTile( title: Text(_s.moveOutServerFuncBtns), subtitle: Text(_s.moveOutServerFuncBtnsHelp, style: textSize13Grey), - trailing: buildSwitch(context, _setting.moveOutServerTabFuncBtns), + trailing: StoreSwitch(prop: _setting.moveOutServerTabFuncBtns), ); } @@ -1051,8 +1038,7 @@ class _SettingPageState extends State { context.pop(); final fontSize = double.tryParse(ctrller.text); if (fontSize == null) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.failed), child: Text('Parsed failed: ${ctrller.text}'), ); @@ -1062,8 +1048,7 @@ class _SettingPageState extends State { property.put(fontSize); } - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.fontSize), child: Input( controller: ctrller, @@ -1085,14 +1070,14 @@ class _SettingPageState extends State { return ListTile( title: Text(_s.sftpRmrfDir), subtitle: Text(_s.sftpRmrfDirSummary, style: grey), - trailing: buildSwitch(context, _setting.sftpRmrfDir), + trailing: StoreSwitch(prop: _setting.sftpRmrfDir), ); } - Widget _buildDoubleColumnServersPage() { - return ListTile( - title: Text(_s.doubleColumnMode), - trailing: buildSwitch(context, _setting.doubleColumnServersPage), - ); - } + // Widget _buildDoubleColumnServersPage() { + // return ListTile( + // title: Text(_s.doubleColumnMode), + // trailing: StoreSwitch(prop: _setting.doubleColumnServersPage), + // ); + // } } diff --git a/lib/view/page/setting/virt_key.dart b/lib/view/page/setting/virt_key.dart index 5ff9874d..71087f12 100644 --- a/lib/view/page/setting/virt_key.dart +++ b/lib/view/page/setting/virt_key.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/order.dart'; import 'package:toolbox/core/utils/platform.dart'; -import 'package:toolbox/core/utils/ui.dart'; import 'package:toolbox/data/model/ssh/virtual_key.dart'; import 'package:toolbox/data/res/ui.dart'; import 'package:toolbox/data/store/setting.dart'; @@ -64,7 +64,7 @@ class _SSHVirtKeySettingPageState extends State { itemCount: allKeys.length, onReorder: (o, n) { if (o >= keys.length || n >= keys.length) { - showSnackBar(context, Text(_s.disabled)); + context.showSnackBar(_s.disabled); return; } keys.moveByItem(keys, o, n, property: _setting.sshVirtKeys); diff --git a/lib/view/page/snippet/edit.dart b/lib/view/page/snippet/edit.dart index 9aede026..445226b3 100644 --- a/lib/view/page/snippet/edit.dart +++ b/lib/view/page/snippet/edit.dart @@ -4,7 +4,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/view/widget/input_field.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/server/snippet.dart'; import '../../../data/provider/snippet.dart'; import '../../../data/res/ui.dart'; @@ -89,7 +88,7 @@ class _SnippetEditPageState extends State final name = _nameController.text; final script = _scriptController.text; if (name.isEmpty || script.isEmpty) { - showSnackBar(context, Text(_s.fieldMustNotEmpty)); + context.showSnackBar(_s.fieldMustNotEmpty); return; } final note = _noteController.text; diff --git a/lib/view/page/snippet/list.dart b/lib/view/page/snippet/list.dart index ef734e93..3d581fbc 100644 --- a/lib/view/page/snippet/list.dart +++ b/lib/view/page/snippet/list.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:provider/provider.dart'; +import 'package:toolbox/core/extension/context.dart'; import 'package:toolbox/core/extension/order.dart'; import '../../../core/utils/misc.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/server/server.dart'; import '../../../data/model/server/snippet.dart'; import '../../../data/provider/server.dart'; @@ -151,8 +151,7 @@ class _SnippetListPageState extends State { ids, results, ).entries.map((e) => '${e.key}:\n${e.value}').join('\n'); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.result), child: Text(result), actions: [ diff --git a/lib/view/page/ssh_term.dart b/lib/view/page/ssh_term.dart index b8d19804..492501e8 100644 --- a/lib/view/page/ssh_term.dart +++ b/lib/view/page/ssh_term.dart @@ -13,7 +13,6 @@ import 'package:xterm/xterm.dart'; import '../../core/route.dart'; import '../../core/utils/platform.dart'; import '../../core/utils/misc.dart'; -import '../../core/utils/ui.dart'; import '../../core/utils/server.dart'; import '../../data/model/server/server_private_info.dart'; import '../../data/model/ssh/virtual_key.dart'; @@ -251,7 +250,7 @@ class _SSHPageState extends State { } break; case VirtualKeyFunc.snippet: - showSnippetDialog(context, _s, (s) { + context.showSnippetDialog(_s, (s) { _terminal.textInput(s.script); _terminal.keyInput(TerminalKey.enter); }); @@ -267,8 +266,7 @@ class _SSHPageState extends State { final idx = cmds.lastIndexWhere((e) => e.toString().contains(echoPWD)); final initPath = cmds[idx + 1].toString(); if (initPath.isEmpty || !initPath.startsWith('/')) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.error), child: const Text('Failed to get current path'), ); @@ -342,7 +340,7 @@ class _SSHPageState extends State { _setupDiscontinuityTimer(); if (_session == null) { - showSnackBar(context, const Text('Null session')); + context.showSnackBar('Null session'); return; } @@ -400,8 +398,7 @@ class _SSHPageState extends State { _discontinuityTimer?.cancel(); if (!mounted) return; _write('\n\nConnection lost\r\n'); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text('${_s.disconnected}\n${_s.goBackQ}'), barrierDismiss: false, diff --git a/lib/view/page/storage/local.dart b/lib/view/page/storage/local.dart index df5b0ba8..b605e396 100644 --- a/lib/view/page/storage/local.dart +++ b/lib/view/page/storage/local.dart @@ -16,7 +16,6 @@ import '../../../core/extension/numx.dart'; import '../../../core/extension/stringx.dart'; import '../../../core/route.dart'; import '../../../core/utils/misc.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/app/path_with_prefix.dart'; import '../../../data/res/path.dart'; import '../../../data/res/ui.dart'; @@ -191,8 +190,7 @@ class _LocalStoragePageState extends State { } Future _showDirActionDialog(FileSystemEntity file) async { - showRoundDialog( - context: context, + context.showRoundDialog( child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -220,8 +218,7 @@ class _LocalStoragePageState extends State { Future _showFileActionDialog(FileSystemEntity file) async { final fileName = file.path.split('/').last; if (widget.isPickFile) { - await showRoundDialog( - context: context, + await context.showRoundDialog( title: Text(_s.pickFile), child: Text(fileName), actions: [ @@ -235,8 +232,7 @@ class _LocalStoragePageState extends State { ]); return; } - showRoundDialog( - context: context, + context.showRoundDialog( child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -247,8 +243,7 @@ class _LocalStoragePageState extends State { context.pop(); final stat = await file.stat(); if (stat.size > editorMaxSize) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.fileTooLarge(fileName, stat.size, '1m')), ); @@ -260,7 +255,7 @@ class _LocalStoragePageState extends State { final f = File(file.absolute.path); if (result != null) { f.writeAsString(result); - showSnackBar(context, Text(_s.saved)); + context.showSnackBar(_s.saved); setState(() {}); } }, @@ -289,8 +284,7 @@ class _LocalStoragePageState extends State { final serverProvider = locator(); final ids = serverProvider.serverOrder; var idx = 0; - await showRoundDialog( - context: context, + await context.showRoundDialog( title: Text(_s.server), child: Picker( items: ids.map((e) => Text(e)).toList(), @@ -319,7 +313,7 @@ class _LocalStoragePageState extends State { file.absolute.path, SftpReqType.upload, )); - showSnackBar(context, Text(_s.added2List)); + context.showSnackBar(_s.added2List); }, ), ListTile( @@ -336,8 +330,7 @@ class _LocalStoragePageState extends State { void _showRenameDialog(FileSystemEntity file) { final fileName = file.path.split('/').last; - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.rename), child: Input( autoFocus: true, @@ -348,7 +341,7 @@ class _LocalStoragePageState extends State { try { file.renameSync(newPath); } catch (e) { - showSnackBar(context, Text('${_s.failed}:\n$e')); + context.showSnackBar('${_s.failed}:\n$e'); return; } @@ -360,8 +353,7 @@ class _LocalStoragePageState extends State { void _showDeleteDialog(FileSystemEntity file) { final fileName = file.path.split('/').last; - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.delete), child: Text(_s.sureDelete(fileName)), actions: [ @@ -375,7 +367,7 @@ class _LocalStoragePageState extends State { try { file.deleteSync(recursive: true); } catch (e) { - showSnackBar(context, Text('${_s.failed}:\n$e')); + context.showSnackBar('${_s.failed}:\n$e'); return; } setState(() {}); diff --git a/lib/view/page/storage/sftp.dart b/lib/view/page/storage/sftp.dart index 87b9619a..dbb90af5 100644 --- a/lib/view/page/storage/sftp.dart +++ b/lib/view/page/storage/sftp.dart @@ -16,7 +16,6 @@ import '../../../core/extension/stringx.dart'; import '../../../core/route.dart'; import '../../../core/utils/misc.dart'; import '../../../core/utils/platform.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/server/server_private_info.dart'; import '../../../data/model/sftp/absolute_path.dart'; import '../../../data/model/sftp/browser_status.dart'; @@ -153,23 +152,22 @@ class _SftpPageState extends State with AfterLayoutMixin { Widget _buildUploadBtn() { return IconButton( onPressed: () async { - final idx = await showRoundDialog( - context: context, + final idx = await context.showRoundDialog( child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: const Icon(Icons.open_in_new), - title: Text(_s.system), - onTap: () => context.pop(1), - ), - ListTile( - leading: const Icon(Icons.folder), - title: Text(_s.inner), - onTap: () => context.pop(0), - ), - ], - )); + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.open_in_new), + title: Text(_s.system), + onTap: () => context.pop(1), + ), + ListTile( + leading: const Icon(Icons.folder), + title: Text(_s.inner), + onTap: () => context.pop(0), + ), + ], + )); final path = await () async { switch (idx) { case 0: @@ -186,7 +184,7 @@ class _SftpPageState extends State with AfterLayoutMixin { } final remotePath = _status.path?.path; if (remotePath == null) { - showSnackBar(context, const Text('remote path is null')); + context.showSnackBar('remote path is null'); return; } _sftp.add( @@ -203,8 +201,7 @@ class _SftpPageState extends State with AfterLayoutMixin { Widget _buildAddBtn() { return IconButton( - onPressed: (() => showRoundDialog( - context: context, + onPressed: (() => context.showRoundDialog( child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -227,8 +224,7 @@ class _SftpPageState extends State with AfterLayoutMixin { return IconButton( padding: const EdgeInsets.all(0), onPressed: () async { - final p = await showRoundDialog( - context: context, + final p = await context.showRoundDialog( title: Text(_s.goto), child: Autocomplete( optionsBuilder: (val) { @@ -358,8 +354,7 @@ class _SftpPageState extends State with AfterLayoutMixin { ), ]); } - showRoundDialog( - context: context, + context.showRoundDialog( child: Column( mainAxisSize: MainAxisSize.min, children: children, @@ -370,13 +365,11 @@ class _SftpPageState extends State with AfterLayoutMixin { Future _edit(BuildContext context, SftpName name) async { final size = name.attr.size; if (size == null || size > editorMaxSize) { - showSnackBar( - context, - Text(_s.fileTooLarge( - name.filename, - size ?? 0, - editorMaxSize, - ))); + context.showSnackBar(_s.fileTooLarge( + name.filename, + size ?? 0, + editorMaxSize, + )); return; } context.pop(); @@ -391,20 +384,19 @@ class _SftpPageState extends State with AfterLayoutMixin { SftpReqType.download, ); _sftp.add(req, completer: completer); - showLoadingDialog(context); + context.showLoadingDialog(); await completer.future; context.pop(); final result = await AppRoute.editor(path: localPath).go(context); if (result != null && result) { _sftp.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload)); - showSnackBar(context, Text(_s.added2List)); + context.showSnackBar(_s.added2List); } } void _download(BuildContext context, SftpName name) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text('${_s.dl2Local(name.filename)}\n${_s.keepForeground}'), actions: [ @@ -441,8 +433,7 @@ class _SftpPageState extends State with AfterLayoutMixin { final dirText = (isDir && !useRmrf) ? '\n${_s.sureDirEmpty}' : ''; final text = '${_s.sureDelete(file.filename)}$dirText'; final child = Text(text); - showRoundDialog( - context: context, + context.showRoundDialog( child: child, title: Text(_s.attention), actions: [ @@ -453,7 +444,7 @@ class _SftpPageState extends State with AfterLayoutMixin { TextButton( onPressed: () async { context.pop(); - showLoadingDialog(context); + context.showLoadingDialog(); final remotePath = _getRemotePath(file); try { if (useRmrf) { @@ -466,8 +457,7 @@ class _SftpPageState extends State with AfterLayoutMixin { context.pop(); } catch (e) { context.pop(); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.error), child: Text(e.toString()), actions: [ @@ -490,8 +480,7 @@ class _SftpPageState extends State with AfterLayoutMixin { void _mkdir(BuildContext context) { context.pop(); final textController = TextEditingController(); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.createFolder), child: Input( autoFocus: true, @@ -507,8 +496,7 @@ class _SftpPageState extends State with AfterLayoutMixin { TextButton( onPressed: () async { if (textController.text == '') { - showRoundDialog( - context: context, + context.showRoundDialog( child: Text(_s.fieldMustNotEmpty), actions: [ TextButton( @@ -533,8 +521,7 @@ class _SftpPageState extends State with AfterLayoutMixin { void _newFile(BuildContext context) { context.pop(); final textController = TextEditingController(); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.createFile), child: Input( autoFocus: true, @@ -546,8 +533,7 @@ class _SftpPageState extends State with AfterLayoutMixin { TextButton( onPressed: () async { if (textController.text == '') { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.fieldMustNotEmpty), actions: [ @@ -561,7 +547,7 @@ class _SftpPageState extends State with AfterLayoutMixin { } context.pop(); final path = '${_status.path!.path}/${textController.text}'; - showLoadingDialog(context); + context.showLoadingDialog(); await _client!.run('touch "$path"'); context.pop(); _listDir(); @@ -575,8 +561,7 @@ class _SftpPageState extends State with AfterLayoutMixin { void _rename(BuildContext context, SftpName file) { context.pop(); final textController = TextEditingController(); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.rename), child: Input( autoFocus: true, @@ -589,8 +574,7 @@ class _SftpPageState extends State with AfterLayoutMixin { TextButton( onPressed: () async { if (textController.text == '') { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.attention), child: Text(_s.fieldMustNotEmpty), actions: [ @@ -617,8 +601,7 @@ class _SftpPageState extends State with AfterLayoutMixin { final absPath = _getRemotePath(name); final cmd = _getDecompressCmd(absPath); if (cmd == null) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(_s.error), child: Text('Unsupport file: ${name.filename}'), actions: [ @@ -630,7 +613,7 @@ class _SftpPageState extends State with AfterLayoutMixin { ); return; } - showLoadingDialog(context); + context.showLoadingDialog(); await _client?.run(cmd); context.pop(); _listDir(); @@ -647,7 +630,7 @@ class _SftpPageState extends State with AfterLayoutMixin { /// Only return true if the path is changed Future _listDir({String? path, SSHClient? client}) async { - showLoadingDialog(context); + context.showLoadingDialog(); if (client != null) { final sftpc = await client.sftp(); _status.client = sftpc; @@ -681,12 +664,11 @@ class _SftpPageState extends State with AfterLayoutMixin { return false; } catch (e, trace) { context.pop(); - _logger.warning('list dir failed', e, trace); + _logger.warning('List dir failed', e, trace); await _backward(); Future.delayed( const Duration(milliseconds: 177), - () => showRoundDialog( - context: context, + () => context.showRoundDialog( title: Text(_s.error), child: Text(e.toString()), actions: [ diff --git a/lib/view/page/storage/sftp_mission.dart b/lib/view/page/storage/sftp_mission.dart index 70e8d369..d632702d 100644 --- a/lib/view/page/storage/sftp_mission.dart +++ b/lib/view/page/storage/sftp_mission.dart @@ -8,7 +8,6 @@ import 'package:toolbox/locator.dart'; import '../../../core/extension/numx.dart'; import '../../../core/utils/misc.dart'; -import '../../../core/utils/ui.dart'; import '../../../data/model/sftp/req.dart'; import '../../../data/provider/sftp.dart'; import '../../../data/res/ui.dart'; @@ -111,8 +110,7 @@ class _SftpMissionPageState extends State { status: status, subtitle: _s.unknown, trailing: IconButton( - onPressed: () => showRoundDialog( - context: context, + onPressed: () => context.showRoundDialog( title: Text(_s.error), child: Text((status.error ?? _s.unknown).toString()), ), @@ -149,8 +147,7 @@ class _SftpMissionPageState extends State { Widget _buildDelete(String name, int id) { return IconButton( - onPressed: () => showRoundDialog( - context: context, + onPressed: () => context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureDelete(name)), actions: [ diff --git a/lib/view/widget/server_func_btns.dart b/lib/view/widget/server_func_btns.dart index 3b1663d1..3b73b81c 100644 --- a/lib/view/widget/server_func_btns.dart +++ b/lib/view/widget/server_func_btns.dart @@ -12,7 +12,6 @@ import '../../core/route.dart'; import '../../core/utils/misc.dart'; import '../../core/utils/platform.dart'; import '../../core/utils/server.dart'; -import '../../core/utils/ui.dart'; import '../../data/model/app/menu.dart'; import '../../data/model/pkg/upgrade_info.dart'; import '../../data/model/server/server_private_info.dart'; @@ -115,8 +114,7 @@ void _onTapMoreBtns( final result = await locator().runSnippets(spi.id, snippets); if (result != null && result.isNotEmpty) { - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(s.result), child: Text(result), actions: [ @@ -186,7 +184,7 @@ Future _gotoSSH( await Process.start("x-terminal-emulator", ["-e"] + sshCommand); break; default: - showSnackBar(context, Text('Mismatch system: $system')); + context.showSnackBar('Mismatch system: $system'); } // For security reason, delete the private key file after use if (shouldGenKey) { @@ -198,7 +196,7 @@ Future _gotoSSH( bool _checkClient(BuildContext context, String id, String msg) { final server = locator().servers[id]; if (server == null || server.client == null) { - showSnackBar(context, Text(msg)); + context.showSnackBar(msg); return false; } return true; @@ -207,14 +205,14 @@ bool _checkClient(BuildContext context, String id, String msg) { Future _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async { final server = locator().servers[spi.id]; if (server == null) { - showSnackBar(context, Text(s.noClient)); + context.showSnackBar(s.noClient); return; } final sys = server.status.sysVer; final pkg = PkgManager.fromDist(sys.dist); // Update pkg list - showLoadingDialog(context); + context.showLoadingDialog(); final updateCmd = pkg?.update; if (updateCmd != null) { await server.client!.execWithPwd( @@ -226,22 +224,22 @@ Future _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async { final listCmd = pkg?.listUpdate; if (listCmd == null) { - showSnackBar(context, Text('Unsupported dist: $sys')); + context.showSnackBar('Unsupported dist: $sys'); return; } // Get upgrade list - showLoadingDialog(context); + context.showLoadingDialog(); final result = await server.client?.run(listCmd).string; context.pop(); if (result == null) { - showSnackBar(context, Text(s.noResult)); + context.showSnackBar(s.noResult); return; } final list = pkg?.updateListRemoveUnused(result.split('\n')); final upgradeable = list?.map((e) => UpgradePkgInfo(e, pkg)).toList(); if (upgradeable == null || upgradeable.isEmpty) { - showSnackBar(context, Text(s.noUpdateAvailable)); + context.showSnackBar(s.noUpdateAvailable); return; } final args = upgradeable.map((e) => e.package).join(' '); @@ -249,8 +247,7 @@ Future _onPkg(BuildContext context, S s, ServerPrivateInfo spi) async { final upgradeCmd = isSU ? pkg?.upgrade(args) : 'sudo ${pkg?.upgrade(args)}'; // Confirm upgrade - final gotoUpgrade = await showRoundDialog( - context: context, + final gotoUpgrade = await context.showRoundDialog( title: Text(s.attention), child: SingleChildScrollView( child: Text('${s.foundNUpdate(upgradeable.length)}\n\n$upgradeCmd'), diff --git a/lib/view/widget/store_switch.dart b/lib/view/widget/store_switch.dart new file mode 100644 index 00000000..0f9f3a60 --- /dev/null +++ b/lib/view/widget/store_switch.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +import '../../core/persistant_store.dart'; + +class StoreSwitch extends StatelessWidget { + final StorePropertyBase prop; + final void Function(bool)? func; + + const StoreSwitch({super.key, required this.prop, this.func}); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: prop.listenable(), + builder: (context, bool value, widget) { + return Switch( + value: value, + onChanged: (value) { + func?.call(value); + prop.put(value); + }, + ); + }, + ); + } +} diff --git a/lib/view/widget/tag.dart b/lib/view/widget/tag.dart index 83d499f3..dba42829 100644 --- a/lib/view/widget/tag.dart +++ b/lib/view/widget/tag.dart @@ -5,7 +5,6 @@ import 'package:toolbox/view/widget/input_field.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../../core/utils/ui.dart'; import '../../data/model/app/tag_pickable.dart'; import '../../data/res/color.dart'; @@ -133,8 +132,7 @@ class _TagEditorState extends State { void _showAddTagDialog() { final textEditingController = TextEditingController(); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(widget.s.add), child: Input( autoFocus: true, @@ -158,8 +156,7 @@ class _TagEditorState extends State { void _showRenameDialog(String tag) { final textEditingController = TextEditingController(text: tag); - showRoundDialog( - context: context, + context.showRoundDialog( title: Text(widget.s.rename), child: Input( autoFocus: true, diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 3d2d5f44..2ee955f4 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 = 545; + CURRENT_PROJECT_VERSION = 547; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; 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 = 545; + CURRENT_PROJECT_VERSION = 547; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; 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 = 545; + CURRENT_PROJECT_VERSION = 547; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.545; + MARKETING_VERSION = 1.0.547; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0;