diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 04f5c2081..44b80d6d1 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 = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -596,7 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -730,7 +730,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -758,7 +758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; 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.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; 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.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; 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.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -902,7 +902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -943,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; 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 = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -981,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/lib/core/extension/context/dialog.dart b/lib/core/extension/context/dialog.dart index 5c905e877..7e09eb2d3 100644 --- a/lib/core/extension/context/dialog.dart +++ b/lib/core/extension/context/dialog.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:choice/choice.dart'; import 'package:flutter/material.dart'; import 'package:toolbox/core/extension/context/common.dart'; @@ -14,11 +16,13 @@ extension DialogX on BuildContext { List? actions, Widget? title, bool barrierDismiss = true, + void Function(BuildContext)? onContext, }) async { return await showDialog( context: this, barrierDismissible: barrierDismiss, - builder: (_) { + builder: (ctx) { + onContext?.call(ctx); return AlertDialog( title: title, content: child, @@ -29,11 +33,28 @@ extension DialogX on BuildContext { ); } - void showLoadingDialog({bool barrierDismiss = false}) { + Future showLoadingDialog({ + required Future Function() fn, + bool barrierDismiss = false, + }) async { + BuildContext? ctx; showRoundDialog( child: UIs.centerSizedLoading, barrierDismiss: barrierDismiss, + onContext: (c) => ctx = c, ); + + try { + return await fn(); + } catch (e) { + rethrow; + } finally { + /// Wait for context to be unmounted + await Future.delayed(const Duration(milliseconds: 100)); + if (ctx?.mounted == true) { + ctx?.pop(); + } + } } Future showPwdDialog( diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index 3e79f3521..5fb3d3c19 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -306,10 +306,13 @@ class ServerProvider extends ChangeNotifier { // Write script to server // by ssh try { - await s.client?.runForOutput(ShellFunc.installShellCmd, - action: (session) async { - session.stdin.add(ShellFunc.allScript.uint8List); - }).string; + await s.client?.runForOutput( + ShellFunc.installShellCmd, + action: (session) async { + session.stdin.add(ShellFunc.allScript.uint8List); + session.stdin.close(); + }, + ); } on SSHAuthAbortError catch (e) { TryLimiter.inc(sid); s.status.err = e.toString(); @@ -387,7 +390,7 @@ class ServerProvider extends ChangeNotifier { if (!systemType.isSegmentsLenMatch(segments.length)) { TryLimiter.inc(sid); s.status.err = - 'Segments not match: expect ${systemType.segmentsLen}, got ${segments.length}'; + 'Segments not match: expect ${systemType.segmentsLen}, got ${segments.length}, raw:\n\n$raw'; _setServerState(s, ServerState.failed); return; } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 803fc1e25..acbb14b4e 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 = 775; - static const String engine = "3.19.0"; - static const String buildAt = "2024-02-23 09:28:28"; - static const int modifications = 2; - static const int script = 38; + static const int build = 778; + static const String engine = "3.19.1"; + static const String buildAt = "2024-02-26 16:30:57"; + static const int modifications = 6; + static const int script = 40; } diff --git a/lib/view/page/backup.dart b/lib/view/page/backup.dart index 64d9f9561..12ddd710e 100644 --- a/lib/view/page/backup.dart +++ b/lib/view/page/backup.dart @@ -231,9 +231,9 @@ class BackupPage extends StatelessWidget { } try { - context.showLoadingDialog(); - final backup = - await Computer.shared.start(Backup.fromJsonString, text.trim()); + final backup = await context.showLoadingDialog( + fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()), + ); if (backupFormatVersion != backup.version) { context.showSnackBar(l10n.backupVersionNotMatch); return; @@ -261,8 +261,6 @@ class BackupPage extends StatelessWidget { } catch (e, trace) { Loggers.app.warning('Import backup failed', e, trace); context.showSnackBar(e.toString()); - } finally { - context.pop(); } } @@ -388,9 +386,10 @@ class BackupPage extends StatelessWidget { } try { - context.showLoadingDialog(); - final backup = - await Computer.shared.start(Backup.fromJsonString, text.trim()); + final backup = await context.showLoadingDialog( + fn: () => Computer.shared.start(Backup.fromJsonString, text.trim()), + ); + if (backupFormatVersion != backup.version) { context.showSnackBar(l10n.backupVersionNotMatch); return; @@ -418,8 +417,6 @@ class BackupPage extends StatelessWidget { } catch (e, trace) { Loggers.app.warning('Import backup failed', e, trace); context.showSnackBar(e.toString()); - } finally { - context.pop(); } } } diff --git a/lib/view/page/container.dart b/lib/view/page/container.dart index 66c3c9ff3..fbca9f7f8 100644 --- a/lib/view/page/container.dart +++ b/lib/view/page/container.dart @@ -72,11 +72,8 @@ class _ContainerPageState extends State { title: TwoLineText(up: l10n.container, down: widget.spi.name), actions: [ IconButton( - onPressed: () async { - context.showLoadingDialog(); - await _container.refresh(); - context.pop(); - }, + onPressed: () => + context.showLoadingDialog(fn: () => _container.refresh()), icon: const Icon(Icons.refresh), ) ], @@ -393,9 +390,10 @@ class _ContainerPageState extends State { TextButton( onPressed: () async { context.pop(); - context.showLoadingDialog(); - final result = await _container.run(cmd); - context.pop(); + + final result = await context.showLoadingDialog( + fn: () => _container.run(cmd), + ); if (result != null) { context.showSnackBar(result.message ?? l10n.unknownError); } @@ -506,9 +504,10 @@ class _ContainerPageState extends State { TextButton( onPressed: () async { context.pop(); - context.showLoadingDialog(); - final result = await _container.delete(id, force); - context.pop(); + + final result = await context.showLoadingDialog( + fn: () => _container.delete(id, force), + ); if (result != null) { context.showRoundDialog( title: Text(l10n.error), @@ -522,9 +521,9 @@ class _ContainerPageState extends State { ); break; case ContainerMenu.start: - context.showLoadingDialog(); - final result = await _container.start(id); - context.pop(); + final result = await context.showLoadingDialog( + fn: () => _container.start(id), + ); if (result != null) { context.showRoundDialog( title: Text(l10n.error), @@ -533,9 +532,9 @@ class _ContainerPageState extends State { } break; case ContainerMenu.stop: - context.showLoadingDialog(); - final result = await _container.stop(id); - context.pop(); + final result = await context.showLoadingDialog( + fn: () => _container.stop(id), + ); if (result != null) { context.showRoundDialog( title: Text(l10n.error), @@ -544,9 +543,9 @@ class _ContainerPageState extends State { } break; case ContainerMenu.restart: - context.showLoadingDialog(); - final result = await _container.restart(id); - context.pop(); + final result = await context.showLoadingDialog( + fn: () => _container.restart(id), + ); if (result != null) { context.showRoundDialog( title: Text(l10n.error), diff --git a/lib/view/page/editor.dart b/lib/view/page/editor.dart index f0041b7a5..b81285628 100644 --- a/lib/view/page/editor.dart +++ b/lib/view/page/editor.dart @@ -148,9 +148,10 @@ 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) { - context.showLoadingDialog(); - await File(widget.path!).writeAsString(_controller.text); - context.pop(); + await context.showLoadingDialog( + fn: () => File(widget.path!).writeAsString(_controller.text), + ); + context.pop(true); return; } diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index 6a69a56b4..1a4215788 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -108,59 +108,60 @@ class _ServerEditPageState extends State { PreferredSizeWidget _buildAppBar() { return CustomAppBar( title: Text(l10n.edit, style: UIs.text18), - actions: widget.spi != null - ? [ - IconButton( - onPressed: () { - var delScripts = false; - context.showRoundDialog( - title: Text(l10n.attention), - child: StatefulBuilder(builder: (ctx, setState) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(l10n.askContinue( - '${l10n.delete} ${l10n.server}(${widget.spi!.name})', - )), - UIs.height13, - if (widget.spi?.server?.canViewDetails ?? false) - CheckboxListTile( - value: delScripts, - onChanged: (_) => setState( - () => delScripts = !delScripts, - ), - controlAffinity: ListTileControlAffinity.leading, - title: Text(l10n.deleteScripts), - tileColor: Colors.transparent, - contentPadding: EdgeInsets.zero, - ) - ], - ); - }), - actions: [ - TextButton( - onPressed: () async { - context.pop(); - if (delScripts) { - context.showLoadingDialog(); - const cmd = - 'rm ${ShellFunc.srvBoxDir}/mobile_v*.sh'; - await widget.spi?.server?.client?.run(cmd); - context.pop(); - } - Pros.server.delServer(widget.spi!.id); - context.pop(true); - }, - child: Text(l10n.ok, style: UIs.textRed), - ), - ], + actions: widget.spi != null ? [_buildDelBtn()] : null, + ); + } + + Widget _buildDelBtn() { + return IconButton( + onPressed: () { + var delScripts = false; + context.showRoundDialog( + title: Text(l10n.attention), + child: StatefulBuilder(builder: (ctx, setState) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l10n.askContinue( + '${l10n.delete} ${l10n.server}(${widget.spi!.name})', + )), + UIs.height13, + if (widget.spi?.server?.canViewDetails ?? false) + CheckboxListTile( + value: delScripts, + onChanged: (_) => setState( + () => delScripts = !delScripts, + ), + controlAffinity: ListTileControlAffinity.leading, + title: Text(l10n.deleteScripts), + tileColor: Colors.transparent, + contentPadding: EdgeInsets.zero, + ) + ], + ); + }), + actions: [ + TextButton( + onPressed: () async { + context.pop(); + if (delScripts) { + await context.showLoadingDialog( + fn: () async { + const cmd = 'rm ${ShellFunc.srvBoxDir}/mobile_v*.sh'; + return widget.spi?.server?.client?.run(cmd); + }, ); - }, - icon: const Icon(Icons.delete), - ), - ] - : null, + } + Pros.server.delServer(widget.spi!.id); + context.pop(true); + }, + child: Text(l10n.ok, style: UIs.textRed), + ), + ], + ); + }, + icon: const Icon(Icons.delete), ); } diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index 96fc983e4..33e7f8087 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -1161,6 +1161,7 @@ class _SettingPageState extends State { _setting.preferTemperatureDevs.put(list); context.pop(); } + return ListTile( title: Text(l10n.preferTemperatureDeviceList), subtitle: Text(l10n.preferTemperatureDeviceListTip, style: UIs.textGrey), diff --git a/lib/view/page/storage/sftp.dart b/lib/view/page/storage/sftp.dart index 94da5db47..79c736f18 100644 --- a/lib/view/page/storage/sftp.dart +++ b/lib/view/page/storage/sftp.dart @@ -406,9 +406,7 @@ class _SftpPageState extends State with AfterLayoutMixin { SftpReqType.download, ); Pros.sftp.add(req, completer: completer); - context.showLoadingDialog(); - await completer.future; - context.pop(); + await context.showLoadingDialog(fn: () => completer.future); final result = await AppRoute.editor(path: localPath).go(context); if (result != null && result) { @@ -471,19 +469,18 @@ class _SftpPageState extends State with AfterLayoutMixin { TextButton( onPressed: () async { context.pop(); - context.showLoadingDialog(); - final remotePath = _getRemotePath(file); try { - if (useRmr) { - await _client!.run('rm -r "$remotePath"'); - } else if (file.attr.isDirectory) { - await _status.client!.rmdir(remotePath); - } else { - await _status.client!.remove(remotePath); - } - context.pop(); + await context.showLoadingDialog(fn: () async { + final remotePath = _getRemotePath(file); + if (useRmr) { + await _client!.run('rm -r "$remotePath"'); + } else if (file.attr.isDirectory) { + await _status.client!.rmdir(remotePath); + } else { + await _status.client!.remove(remotePath); + } + }); } catch (e) { - context.pop(); context.showRoundDialog( title: Text(l10n.error), child: Text(e.toString()), @@ -574,9 +571,8 @@ class _SftpPageState extends State with AfterLayoutMixin { } context.pop(); final path = '${_status.path!.path}/${textController.text}'; - context.showLoadingDialog(); - await _client!.run('touch "$path"'); - context.pop(); + await context.showLoadingDialog( + fn: () => _client!.run('touch "$path"')); _listDir(); }, child: Text(l10n.ok, style: UIs.textRed), @@ -640,9 +636,7 @@ class _SftpPageState extends State with AfterLayoutMixin { ); return; } - context.showLoadingDialog(); - await _client?.run(cmd); - context.pop(); + await context.showLoadingDialog(fn: () async => _client?.run(cmd)); _listDir(); } @@ -657,67 +651,65 @@ class _SftpPageState extends State with AfterLayoutMixin { /// Only return true if the path is changed Future _listDir() async { - // Allow dismiss, because may this op will take a long time - context.showLoadingDialog(barrierDismiss: true); - if (_status.client == null) { - final sftpc = await _client?.sftp(); - _status.client = sftpc; - } - try { - final listPath = _status.path?.path ?? '/'; - final fs = await _status.client?.listdir(listPath); - if (fs == null) { - return false; - } - fs.sort((a, b) => a.filename.compareTo(b.filename)); + return context.showLoadingDialog( + fn: () async { + _status.client ??= await _client?.sftp(); + try { + final listPath = _status.path?.path ?? '/'; + final fs = await _status.client?.listdir(listPath); + if (fs == null) { + return false; + } + fs.sort((a, b) => a.filename.compareTo(b.filename)); - /// Issue #97 - /// In order to compatible with the Synology NAS - /// which not has '.' and '..' in listdir - if (fs.isNotEmpty && fs.first.filename == '.') { - fs.removeAt(0); - } + /// Issue #97 + /// In order to compatible with the Synology NAS + /// which not has '.' and '..' in listdir + if (fs.isNotEmpty && fs.first.filename == '.') { + fs.removeAt(0); + } - /// Issue #96 - /// Due to [WillPopScope] added in this page - /// There is no need to keep '..' folder in listdir - /// So remove it - if (fs.isNotEmpty && fs.first.filename == '..') { - fs.removeAt(0); - } - if (mounted) { - setState(() { - _status.files = fs; - }); - context.pop(); - - // Only update history when success - if (Stores.setting.sftpOpenLastPath.fetch()) { - Stores.history.sftpLastPath.put(widget.spi.id, listPath); - } + /// Issue #96 + /// Due to [WillPopScope] added in this page + /// There is no need to keep '..' folder in listdir + /// So remove it + if (fs.isNotEmpty && fs.first.filename == '..') { + fs.removeAt(0); + } + if (mounted) { + setState(() { + _status.files = fs; + }); + + // Only update history when success + if (Stores.setting.sftpOpenLastPath.fetch()) { + Stores.history.sftpLastPath.put(widget.spi.id, listPath); + } - return true; - } - return false; - } catch (e, trace) { - context.pop(); - Loggers.app.warning('List dir failed', e, trace); - await _backward(); - Future.delayed( - const Duration(milliseconds: 177), - () => context.showRoundDialog( - title: Text(l10n.error), - child: Text(e.toString()), - actions: [ - TextButton( - onPressed: () => context.pop(), - child: Text(l10n.ok), - ) - ], - ), - ); - return false; - } + return true; + } + return false; + } catch (e, trace) { + Loggers.app.warning('List dir failed', e, trace); + await _backward(); + Future.delayed( + const Duration(milliseconds: 177), + () => context.showRoundDialog( + title: Text(l10n.error), + child: Text(e.toString()), + actions: [ + TextButton( + onPressed: () => context.pop(), + child: Text(l10n.ok), + ) + ], + ), + ); + return false; + } + }, + barrierDismiss: true, + ); } Future _backward() async { diff --git a/lib/view/widget/server_func_btns.dart b/lib/view/widget/server_func_btns.dart index 0afee9ee3..f10c8fafe 100644 --- a/lib/view/widget/server_func_btns.dart +++ b/lib/view/widget/server_func_btns.dart @@ -223,7 +223,8 @@ bool _checkClient(BuildContext context, String id) { Future _onPkg(BuildContext context, ServerPrivateInfo spi) async { final server = spi.server; - if (server == null) { + final client = server?.client; + if (server == null || client == null) { context.showSnackBar(l10n.noClient); return; } @@ -232,42 +233,43 @@ Future _onPkg(BuildContext context, ServerPrivateInfo spi) async { context.showSnackBar(l10n.noResult); return; } + final pkg = PkgManager.fromDist(sys.dist); + if (pkg == null) { + context.showSnackBar('Unsupported dist: $sys'); + return; + } // Update pkg list - context.showLoadingDialog(); - final updateCmd = pkg?.update; - if (updateCmd != null) { - await server.client!.execWithPwd( - updateCmd, - context: context, - ); - } - context.pop(); + await context.showLoadingDialog( + fn: () async { + final updateCmd = pkg.update; + if (updateCmd != null) { + await client.execWithPwd(updateCmd, context: context); + } + }, + barrierDismiss: true, + ); - final listCmd = pkg?.listUpdate; + final listCmd = pkg.listUpdate; if (listCmd == null) { context.showSnackBar('Unsupported dist: $sys'); return; } // Get upgrade list - context.showLoadingDialog(); - final result = await server.client?.run(listCmd).string; - context.pop(); - if (result == null) { - context.showSnackBar(l10n.noResult); - return; - } - final list = pkg?.updateListRemoveUnused(result.split('\n')); - final upgradeable = list?.map((e) => UpgradePkgInfo(e, pkg)).toList(); - if (upgradeable == null || upgradeable.isEmpty) { + final result = await context.showLoadingDialog(fn: () async { + return await client.run(listCmd).string; + }); + final list = pkg.updateListRemoveUnused(result.split('\n')); + final upgradeable = list.map((e) => UpgradePkgInfo(e, pkg)).toList(); + if (upgradeable.isEmpty) { context.showSnackBar(l10n.noUpdateAvailable); return; } final args = upgradeable.map((e) => e.package).join(' '); final isSU = server.spi.user == 'root'; - final upgradeCmd = isSU ? pkg?.upgrade(args) : 'sudo ${pkg?.upgrade(args)}'; + final upgradeCmd = isSU ? pkg.upgrade(args) : 'sudo ${pkg.upgrade(args)}'; // Confirm upgrade final gotoUpgrade = await context.showRoundDialog( diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 53326295d..b8d14766e 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -439,7 +439,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_TEAM = BA88US33G6; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; @@ -449,7 +449,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "Server Box"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -574,7 +574,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_TEAM = BA88US33G6; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; @@ -584,7 +584,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "Server Box"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -604,7 +604,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 775; + CURRENT_PROJECT_VERSION = 778; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6; INFOPLIST_FILE = Runner/Info.plist; @@ -615,7 +615,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.0.775; + MARKETING_VERSION = 1.0.778; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "Server Box"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/test/nvidia_test.dart b/test/nvidia_test.dart index edba48058..27ca9f366 100644 --- a/test/nvidia_test.dart +++ b/test/nvidia_test.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:toolbox/data/model/server/nvdia.dart'; @@ -841,8 +840,24 @@ const _raw = ''' void main() { test('nvdia-smi', () { - if (kDebugMode) { - print(NvidiaSmi.fromXml(_raw).firstOrNull?.memory.processes); - } + final items = NvidiaSmi.fromXml(_raw); + expect(items.length, 1); + final item = items[0]; + expect(item.name, 'NVIDIA GeForce RTX 3080 Ti'); + expect(item.temp, 34); + expect(item.power, '24.55 W / 350.00 W'); + expect(item.memory.total, 12288); + expect(item.memory.used, 352); + expect(item.memory.unit, 'MiB'); + final processes = item.memory.processes; + expect(processes.length, 3); + expect(processes[0].pid, 1575); + expect(processes[0].name, '/usr/lib/xorg/Xorg'); + expect(processes[0].memory, 220); + expect(processes[1].pid, 1933); + expect(processes[1].name, '/usr/bin/gnome-shell'); + expect(processes[1].memory, 34); + expect(processes[2].pid, 16484); + expect(processes[2].memory, 76); }); }