diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 45fd157e..01c132ea 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 = 700; + CURRENT_PROJECT_VERSION = 701; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -596,7 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -730,7 +730,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -758,7 +758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; 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.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; 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.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; 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.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -902,7 +902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -943,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.700; + MARKETING_VERSION = 1.0.701; 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 = 700; + CURRENT_PROJECT_VERSION = 701; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -981,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.700; + MARKETING_VERSION = 1.0.701; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/lib/core/extension/datetime.dart b/lib/core/extension/datetime.dart index 0326c0e7..b6af20ec 100644 --- a/lib/core/extension/datetime.dart +++ b/lib/core/extension/datetime.dart @@ -2,4 +2,14 @@ extension DateTimeX on DateTime { String get hourMinute { return '${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}'; } + + /// Format: 2021-01-01-0000 + String get numStr { + final year = this.year.toString(); + final month = this.month.toString().padLeft(2, '0'); + final day = this.day.toString().padLeft(2, '0'); + final hour = this.hour.toString().padLeft(2, '0'); + final minute = this.minute.toString().padLeft(2, '0'); + return '$year-$month-$day-$hour$minute'; + } } diff --git a/lib/core/utils/sync/webdav.dart b/lib/core/utils/sync/webdav.dart index 6da83263..1f0a82fe 100644 --- a/lib/core/utils/sync/webdav.dart +++ b/lib/core/utils/sync/webdav.dart @@ -73,6 +73,21 @@ abstract final class Webdav { return null; } + static Future> list() async { + try { + final list = await _client.readDir(_prefix); + final names = []; + for (final item in list) { + if ((item.isDir ?? true) || item.name == null) continue; + names.add(item.name!); + } + return names; + } catch (e, s) { + _logger.warning('List failed', e, s); + return []; + } + } + static void changeClient(String url, String user, String pwd) { _client = WebdavClient(url: url, user: user, pwd: pwd); Stores.setting.webdavUrl.put(url); diff --git a/lib/data/model/app/backup.dart b/lib/data/model/app/backup.dart index ffc49f62..94a7cfd9 100644 --- a/lib/data/model/app/backup.dart +++ b/lib/data/model/app/backup.dart @@ -76,9 +76,9 @@ class Backup { lastModTime = Stores.lastModTime, history = Stores.history.box.toJson(); - static Future backup() async { + static Future backup([String? name]) async { final result = _diyEncrypt(json.encode(Backup.loadFromStore())); - final path = await Paths.bak; + final path = '${await Paths.doc}/${name ?? Paths.bakName}'; await File(path).writeAsString(result); return path; } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 8592d9a8..d2adbf98 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 = 700; + static const int build = 701; static const String engine = "3.16.5"; - static const String buildAt = "2024-01-06 11:00:48"; - static const int modifications = 4; + static const String buildAt = "2024-01-06 13:39:30"; + static const int modifications = 1; static const int script = 34; } diff --git a/lib/view/page/backup.dart b/lib/view/page/backup.dart index 4b278b98..7ab993b0 100644 --- a/lib/view/page/backup.dart +++ b/lib/view/page/backup.dart @@ -6,6 +6,7 @@ import 'package:toolbox/core/extension/context/common.dart'; import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/context/snackbar.dart'; +import 'package:toolbox/core/extension/datetime.dart'; import 'package:toolbox/core/utils/misc.dart'; import 'package:toolbox/core/utils/sync/icloud.dart'; import 'package:toolbox/core/utils/platform/base.dart'; @@ -122,8 +123,7 @@ class BackupPage extends StatelessWidget { child: ExpandTile( leading: const Icon(Icons.storage), title: const Text('WebDAV'), - initiallyExpanded: - !(isIOS || isMacOS) && Stores.setting.webdavSync.fetch(), + initiallyExpanded: true, children: [ ListTile( title: Text(l10n.setting), @@ -241,21 +241,48 @@ class BackupPage extends StatelessWidget { Future _onTapWebdavDl(BuildContext context) async { webdavLoading.value = true; - try { - final result = await Webdav.download( - relativePath: Paths.bakName, - ); - if (result != null) { - Loggers.app.warning('Download webdav backup failed: $result'); - return; - } - } catch (e, s) { - Loggers.app.warning('Download webdav backup failed', e, s); - context.showSnackBar(e.toString()); + final files = await Webdav.list(); + if (files.isEmpty) { + context.showSnackBar(l10n.dirEmpty); webdavLoading.value = false; return; } - final dlFile = await File(await Paths.bak).readAsString(); + + final fileName = await context.showRoundDialog( + title: Text(l10n.restore), + child: SizedBox( + width: 300, + height: 300, + child: ListView.builder( + itemCount: files.length, + itemBuilder: (_, index) { + final file = files[index]; + return ListTile( + title: Text(file), + onTap: () => context.pop(file), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => context.pop(), + child: Text(l10n.cancel), + ), + ], + ); + if (fileName == null) { + webdavLoading.value = false; + return; + } + + final result = await Webdav.download(relativePath: fileName); + if (result != null) { + Loggers.app.warning('Download webdav backup failed: $result'); + webdavLoading.value = false; + return; + } + final dlFile = await File(fileName).readAsString(); final dlBak = await compute(Backup.fromJsonString, dlFile); await dlBak.restore(force: true); webdavLoading.value = false; @@ -263,8 +290,9 @@ class BackupPage extends StatelessWidget { Future _onTapWebdavUp(BuildContext context) async { webdavLoading.value = true; - await Backup.backup(); - final uploadResult = await Webdav.upload(relativePath: Paths.bakName); + final bakName = '${DateTime.now().numStr}-${Paths.bakName}'; + await Backup.backup(bakName); + final uploadResult = await Webdav.upload(relativePath: bakName); if (uploadResult != null) { Loggers.app.warning('Upload webdav backup failed: $uploadResult'); } else { diff --git a/lib/view/widget/input_field.dart b/lib/view/widget/input_field.dart index 84408f7c..94687581 100644 --- a/lib/view/widget/input_field.dart +++ b/lib/view/widget/input_field.dart @@ -59,7 +59,7 @@ class _InputState extends State { Widget build(BuildContext context) { return CardX( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 7), + padding: const EdgeInsets.symmetric(horizontal: 15), child: TextField( controller: widget.controller, maxLines: widget.maxLines,