diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index 285f2401..e62854e2 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -411,6 +411,12 @@ abstract class S { /// **'Extra args'** String get extraArgs; + /// No description provided for @failed. + /// + /// In en, this message translates to: + /// **'Failed'** + String get failed; + /// No description provided for @feedback. /// /// In en, this message translates to: @@ -639,6 +645,12 @@ abstract class S { /// **'Name'** String get name; + /// No description provided for @needRestart. + /// + /// In en, this message translates to: + /// **'Need to restart app'** + String get needRestart; + /// No description provided for @newContainer. /// /// In en, this message translates to: @@ -687,6 +699,12 @@ abstract class S { /// **'No update available'** String get noUpdateAvailable; + /// No description provided for @notSelected. + /// + /// In en, this message translates to: + /// **'Not selected'** + String get notSelected; + /// No description provided for @nullToken. /// /// In en, this message translates to: @@ -819,6 +837,12 @@ abstract class S { /// **'Please report bugs on {url}'** String reportBugsOnGithubIssue(Object url); + /// No description provided for @restart. + /// + /// In en, this message translates to: + /// **'Restart'** + String get restart; + /// No description provided for @restore. /// /// In en, this message translates to: @@ -1112,12 +1136,6 @@ abstract class S { /// In en, this message translates to: /// **'Will take effect immediately'** String get willTakEeffectImmediately; - - /// No description provided for @notSelected. - /// - /// In en, this message translates to: - /// **'Not selected'** - String get notSelected; } class _SDelegate extends LocalizationsDelegate { diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index fbb9d454..ee8dfaa1 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -173,6 +173,9 @@ class SEn extends S { @override String get extraArgs => 'Extra args'; + @override + String get failed => 'Failed'; + @override String get feedback => 'Feedback'; @@ -299,6 +302,9 @@ class SEn extends S { @override String get name => 'Name'; + @override + String get needRestart => 'Need to restart app'; + @override String get newContainer => 'New container'; @@ -323,6 +329,9 @@ class SEn extends S { @override String get noUpdateAvailable => 'No update available'; + @override + String get notSelected => 'Not selected'; + @override String get nullToken => 'Null token'; @@ -391,6 +400,9 @@ class SEn extends S { return 'Please report bugs on $url'; } + @override + String get restart => 'Restart'; + @override String get restore => 'Restore'; @@ -557,7 +569,4 @@ class SEn extends S { @override String get willTakEeffectImmediately => 'Will take effect immediately'; - - @override - String get notSelected => 'Not selected'; } diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index f321304d..ee8d8fb5 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -173,6 +173,9 @@ class SZh extends S { @override String get extraArgs => '额外参数'; + @override + String get failed => '失败'; + @override String get feedback => '反馈'; @@ -299,6 +302,9 @@ class SZh extends S { @override String get name => '名称'; + @override + String get needRestart => '需要重启 App'; + @override String get newContainer => '新建容器'; @@ -323,6 +329,9 @@ class SZh extends S { @override String get noUpdateAvailable => '没有可用更新'; + @override + String get notSelected => '未选择'; + @override String get nullToken => '无Token'; @@ -391,6 +400,9 @@ class SZh extends S { return '请到 $url 提交问题'; } + @override + String get restart => '重启'; + @override String get restore => '恢复'; @@ -557,7 +569,4 @@ class SZh extends S { @override String get willTakEeffectImmediately => '更改将会立即生效'; - - @override - String get notSelected => '未选择'; } diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index c5a6d5e6..7cbdcf03 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -359,7 +359,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -367,7 +367,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.237; + MARKETING_VERSION = 1.0.239; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -490,7 +490,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -498,7 +498,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.237; + MARKETING_VERSION = 1.0.239; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -515,7 +515,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 239; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -523,7 +523,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.237; + MARKETING_VERSION = 1.0.239; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/core/utils/misc.dart b/lib/core/utils/misc.dart index d776c3ef..a1a808b2 100644 --- a/lib/core/utils/misc.dart +++ b/lib/core/utils/misc.dart @@ -7,6 +7,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:plain_notification_token/plain_notification_token.dart'; import 'package:share_plus/share_plus.dart'; +import '../../view/widget/rebuild.dart'; import 'platform.dart'; Future shareFiles(BuildContext context, List filePaths) async { @@ -54,3 +55,7 @@ String? getFileName(String? path) { } return path.split('/').last; } + +void rebuildAll(BuildContext context) { + RebuildWidget.restartApp(context); +} diff --git a/lib/data/provider/sftp_download.dart b/lib/data/provider/sftp_download.dart index 978e260e..b5c598b5 100644 --- a/lib/data/provider/sftp_download.dart +++ b/lib/data/provider/sftp_download.dart @@ -3,7 +3,7 @@ import 'package:toolbox/core/provider_base.dart'; import '../model/sftp/download_item.dart'; import '../model/sftp/download_status.dart'; -class SftpDownloadProvider extends ProviderBase { +class SftpProvider extends ProviderBase { final List _status = []; List get status => _status; diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 9652f5d0..b122941a 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 = 237; + static const int build = 239; static const String engine = - "Flutter 3.7.7 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 2ad6cd72c0 (10 days ago) • 2023-03-08 09:41:59 -0800\nEngine • revision 1837b5be5f\nTools • Dart 2.19.4 • DevTools 2.20.1\n"; - static const String buildAt = "2023-03-18 17:38:10.546482"; - static const int modifications = 4; + "Flutter 3.7.7 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 2ad6cd72c0 (13 days ago) • 2023-03-08 09:41:59 -0800\nEngine • revision 1837b5be5f\nTools • Dart 2.19.4 • DevTools 2.20.1\n"; + static const String buildAt = "2023-03-21 14:03:13.656226"; + static const int modifications = 17; } diff --git a/lib/data/res/path.dart b/lib/data/res/path.dart index 15460eaf..05da9443 100644 --- a/lib/data/res/path.dart +++ b/lib/data/res/path.dart @@ -8,3 +8,8 @@ Future get sftpDownloadDir async { final dir = Directory('${(await docDir).path}/sftp'); return dir.create(recursive: true); } + +Future get fontDir async { + final dir = Directory('${(await docDir).path}/font'); + return dir.create(recursive: true); +} diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 29e3750e..69b8f9fb 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -34,5 +34,5 @@ class SettingStore extends PersistentStore { StoreProperty get themeMode => property('themeMode', defaultValue: 0); /// Font file path - StoreProperty get fontPath => property('fontPath'); + StoreProperty get fontPath => property('fontPath2'); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 524265bc..16012c28 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -52,6 +52,7 @@ "experimentalFeature": "Experimental feature", "export": "Export", "extraArgs": "Extra args", + "failed": "Failed", "feedback": "Feedback", "feedbackOnGithub": "If you have any questions, please feedback on Github.", "fieldMustNotEmpty": "These fields must not be empty.", @@ -90,6 +91,7 @@ "min": "min", "ms": "ms", "name": "Name", + "needRestart": "Need to restart app", "newContainer": "New container", "noClient": "No client", "noInterface": "No interface", @@ -98,6 +100,7 @@ "noSavedSnippet": "No saved snippets.", "noServerAvailable": "No server available.", "noUpdateAvailable": "No update available", + "notSelected": "Not selected", "nullToken": "Null token", "ok": "OK", "onServerDetailPage": "On server detail page", @@ -120,6 +123,7 @@ "pwd": "Password", "rename": "Rename", "reportBugsOnGithubIssue": "Please report bugs on {url}", + "restart": "Restart", "restore": "Restore", "restoreSuccess": "Restore success. Restart app to apply.", "restoreSureWithDate": "Are you sure to restore from {date} ?", @@ -168,6 +172,5 @@ "versionUnknownUpdate": "Current: v1.0.{build}", "versionUpdated": "Current: v1.0.{build}, is up to date", "waitConnection": "Please wait for the connection to be established.", - "willTakEeffectImmediately": "Will take effect immediately", - "notSelected": "Not selected" + "willTakEeffectImmediately": "Will take effect immediately" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 0af46d2c..a24694aa 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -52,6 +52,7 @@ "experimentalFeature": "实验性功能", "export": "导出", "extraArgs": "额外参数", + "failed": "失败", "feedback": "反馈", "feedbackOnGithub": "如果你有任何问题,请在GitHub反馈", "fieldMustNotEmpty": "这些输入框不能为空。", @@ -90,6 +91,7 @@ "min": "最小", "ms": "毫秒", "name": "名称", + "needRestart": "需要重启 App", "newContainer": "新建容器", "noClient": "没有SSH连接", "noInterface": "没有可用的接口", @@ -98,6 +100,7 @@ "noSavedSnippet": "没有已保存的代码片段。", "noServerAvailable": "没有可用的服务器。", "noUpdateAvailable": "没有可用更新", + "notSelected": "未选择", "nullToken": "无Token", "ok": "好", "onServerDetailPage": "在服务器详情页", @@ -120,6 +123,7 @@ "pwd": "密码", "rename": "重命名", "reportBugsOnGithubIssue": "请到 {url} 提交问题", + "restart": "重启", "restore": "恢复", "restoreSuccess": "恢复成功,需要重启App来应用更改", "restoreSureWithDate": "确定恢复 {date} 的备份吗?", @@ -168,6 +172,5 @@ "versionUnknownUpdate": "当前:v1.0.{build}", "versionUpdated": "当前:v1.0.{build}, 已是最新版本", "waitConnection": "请等待连接建立", - "willTakEeffectImmediately": "更改将会立即生效", - "notSelected": "未选择" + "willTakEeffectImmediately": "更改将会立即生效" } \ No newline at end of file diff --git a/lib/locator.dart b/lib/locator.dart index e6416e1f..1a4c52db 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -31,7 +31,7 @@ void setupLocatorForProviders() { locator.registerSingleton(VirtualKeyboard()); locator.registerSingleton(SnippetProvider()); locator.registerSingleton(PrivateKeyProvider()); - locator.registerSingleton(SftpDownloadProvider()); + locator.registerSingleton(SftpProvider()); } Future setupLocatorForStores() async { diff --git a/lib/main.dart b/lib/main.dart index 33600155..c8b7f49d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,11 +4,10 @@ import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; -import 'package:toolbox/core/utils/ui.dart'; -import 'package:toolbox/data/store/setting.dart'; import 'app.dart'; import 'core/analysis.dart'; +import 'core/utils/ui.dart'; import 'data/model/server/private_key_info.dart'; import 'data/model/server/server_private_info.dart'; import 'data/model/server/snippet.dart'; @@ -21,7 +20,9 @@ import 'data/provider/server.dart'; import 'data/provider/sftp_download.dart'; import 'data/provider/snippet.dart'; import 'data/provider/virtual_keyboard.dart'; +import 'data/store/setting.dart'; import 'locator.dart'; +import 'view/widget/rebuild.dart'; late final DebugProvider _debug; @@ -91,10 +92,11 @@ Future main() async { ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), ChangeNotifierProvider(create: (_) => locator()), - ChangeNotifierProvider( - create: (_) => locator()), + ChangeNotifierProvider(create: (_) => locator()), ], - child: MyApp(), + child: RebuildWidget( + child: MyApp(), + ), ), ); }); diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 798e23b7..53853589 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -443,8 +443,10 @@ class _ServerPageState extends State @override Future afterFirstLayout(BuildContext context) async { await GetIt.I.allReady(); - await _serverProvider.loadLocalData(); - await _serverProvider.refreshData(); - _serverProvider.startAutoRefresh(); + if (_serverProvider.servers.isEmpty) { + await _serverProvider.loadLocalData(); + await _serverProvider.refreshData(); + _serverProvider.startAutoRefresh(); + } } } diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index 3c66ecb9..92dfba63 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -1,7 +1,10 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_material_color_picker/flutter_material_color_picker.dart'; import 'package:provider/provider.dart'; +import 'package:toolbox/data/res/path.dart'; import '../../core/utils/misc.dart'; import '../../core/utils/platform.dart'; @@ -448,18 +451,52 @@ class _SettingPageState extends State { } Widget _buildFont() { - return ListTile( + return ExpansionTile( title: Text(_s.chooseFontFile), - subtitle: Text(getFileName(_setting.fontPath.fetch()) ?? _s.notSelected), - trailing: TextButton( - onPressed: () async { - final path = await pickOneFile(); - if (path != null) { - _setting.fontPath.put(path); - setState(() {}); - } - }, - child: Text(_s.pickFile)), + trailing: Text(getFileName(_setting.fontPath.fetch()) ?? _s.notSelected), + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton( + onPressed: () async => pickFontFile(), + child: Text(_s.pickFile), + ), + TextButton( + onPressed: () => setState(() { + _setting.fontPath.delete(); + showSnackBarWithAction( + context, + '${_s.success}\n${_s.needRestart}', + _s.restart, + () => rebuildAll(context), + ); + }), + child: Text(_s.clear), + ) + ], + ) + ], ); } + + Future pickFontFile() async { + final path = await pickOneFile(); + if (path != null) { + final fontDir_ = await fontDir; + final fontFile = File(path); + final newPath = '${fontDir_.path}/${path.split('/').last}'; + await fontFile.copy(newPath); + _setting.fontPath.put(newPath); + setState(() {}); + showSnackBarWithAction( + context, + '${_s.success}\n${_s.needRestart}', + _s.restart, + () => rebuildAll(context), + ); + return; + } + showSnackBar(context, Text(_s.failed)); + } } diff --git a/lib/view/page/sftp/downloading.dart b/lib/view/page/sftp/downloading.dart index 591fc578..34de5bc7 100644 --- a/lib/view/page/sftp/downloading.dart +++ b/lib/view/page/sftp/downloading.dart @@ -41,7 +41,7 @@ class _SFTPDownloadingPageState extends State { } Widget _buildBody() { - return Consumer(builder: (__, pro, _) { + return Consumer(builder: (__, pro, _) { if (pro.status.isEmpty) { return Center( child: Text(_s.sftpNoDownloadTask), diff --git a/lib/view/page/sftp/view.dart b/lib/view/page/sftp/view.dart index d5ad2c3d..a29e93b0 100644 --- a/lib/view/page/sftp/view.dart +++ b/lib/view/page/sftp/view.dart @@ -305,7 +305,7 @@ class _SFTPPageState extends State { final local = '${(await sftpDownloadDir).path}$remotePath'; final pubKeyId = widget.spi.pubKeyId; - locator().add( + locator().add( DownloadItem( widget.spi, remotePath, diff --git a/lib/view/page/ssh.dart b/lib/view/page/ssh.dart index 52f0077d..ced5c8b0 100644 --- a/lib/view/page/ssh.dart +++ b/lib/view/page/ssh.dart @@ -34,6 +34,7 @@ class _SSHPageState extends State { late final _terminal = Terminal(inputHandler: _keyboard); SSHClient? _client; final _keyboard = locator(); + final _setting = locator(); late MediaQueryData _media; final _virtualKeyboardHeight = 57.0; final TerminalController _terminalController = TerminalController(); @@ -47,7 +48,7 @@ class _SSHPageState extends State { @override void initState() { super.initState(); - final termColorIdx = locator().termColorIdx.fetch()!; + final termColorIdx = _setting.termColorIdx.fetch()!; _termColors = TerminalColorsPlatform.values[termColorIdx].colors; initTerminal(); } @@ -149,6 +150,8 @@ class _SSHPageState extends State { _terminal, controller: _terminalController, keyboardType: TextInputType.visiblePassword, + textStyle: TerminalStyle.fromTextStyle( + TextStyle(fontFamily: getFileName(_setting.fontPath.fetch()))), theme: termTheme, deleteDetection: isIOS, onTapUp: _onTapUp, diff --git a/lib/view/widget/rebuild.dart b/lib/view/widget/rebuild.dart new file mode 100644 index 00000000..0d96bd71 --- /dev/null +++ b/lib/view/widget/rebuild.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class RebuildWidget extends StatefulWidget { + const RebuildWidget({super.key, required this.child}); + + final Widget child; + + static void restartApp(BuildContext context) { + context.findAncestorStateOfType<_RebuildWidgetState>()?.restartApp(); + } + + @override + _RebuildWidgetState createState() => _RebuildWidgetState(); +} + +class _RebuildWidgetState extends State { + Key key = UniqueKey(); + + void restartApp() { + setState(() { + key = UniqueKey(); + }); + } + + @override + Widget build(BuildContext context) { + return KeyedSubtree( + key: key, + child: widget.child, + ); + } +}