From a97b3cf43e1b312c6524cd8535f0ce988bcf20db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:11:47 +0800 Subject: [PATCH] opt.: bak pwd is optional (#872) --- lib/core/sync.dart | 6 +-- lib/data/model/app/bak/backup_service.dart | 51 ++-------------------- lib/data/provider/server.freezed.dart | 5 +-- lib/data/res/github_id.dart | 1 + lib/view/page/backup.dart | 44 ++++++++++++++----- 5 files changed, 38 insertions(+), 69 deletions(-) diff --git a/lib/core/sync.dart b/lib/core/sync.dart index 1b0fc694..ab0c6268 100644 --- a/lib/core/sync.dart +++ b/lib/core/sync.dart @@ -14,11 +14,7 @@ final class BakSyncer extends SyncIface { @override Future saveToFile() async { final pwd = await SecureStoreProps.bakPwd.read(); - if (pwd == null || pwd.isEmpty) { - // Enforce password for non-clipboard backups - throw Exception('Backup password not set'); - } - await BackupV2.backup(null, pwd); + await BackupV2.backup(null, pwd?.isEmpty == true ? null : pwd); } @override diff --git a/lib/data/model/app/bak/backup_service.dart b/lib/data/model/app/bak/backup_service.dart index 1a6e0bac..396ff5f5 100644 --- a/lib/data/model/app/bak/backup_service.dart +++ b/lib/data/model/app/bak/backup_service.dart @@ -11,27 +11,10 @@ class BackupService { /// Perform backup operation with the given source static Future backup(BuildContext context, BackupSource source) async { try { - String? password; + final saved = await SecureStoreProps.bakPwd.read(); + final password = saved?.isEmpty == true ? null : saved; - if (source is ClipboardBackupSource) { - // Clipboard backup: allow optional password - password = await _getClipboardPassword(context); - if (password == null) return; // canceled - } else { - // All other backups require pre-set bakPwd (SecureStore) - final saved = await SecureStoreProps.bakPwd.read(); - if (saved == null || saved.isEmpty) { - // Prompt to set before proceeding - password = await _showPasswordDialog(context, hint: l10n.backupPasswordTip); - if (password == null || password.isEmpty) return; // Not set - await SecureStoreProps.bakPwd.write(password); - context.showSnackBar(l10n.backupPasswordSet); - } else { - password = saved; - } - } - - final path = await BackupV2.backup(null, password.isEmpty ? null : password); + final path = await BackupV2.backup(null, password?.isEmpty == true ? null : password); await source.saveContent(path); if (source is ClipboardBackupSource) { @@ -56,34 +39,6 @@ class BackupService { await restoreFromText(context, text); } - /// Handle password dialog for backup operations - static Future _getClipboardPassword(BuildContext context) async { - // Use saved bakPwd as default for clipboard flow if exists, but allow empty/custom - final savedPassword = await SecureStoreProps.bakPwd.read(); - String? password; - - if (savedPassword != null && savedPassword.isNotEmpty) { - final useCustom = await context.showRoundDialog( - title: l10n.backupPassword, - child: Text(l10n.backupPasswordTip), - actions: [ - Btn.cancel(), - TextButton(onPressed: () => context.pop(false), child: Text(l10n.backupPasswordSet)), - TextButton(onPressed: () => context.pop(true), child: Text(libL10n.custom)), - ], - ); - if (useCustom == null) return null; - if (useCustom) { - password = await _showPasswordDialog(context, initial: savedPassword); - } else { - password = savedPassword; - } - } else { - password = await _showPasswordDialog(context); - } - return password; - } - /// Handle restore from text with decryption support static Future restoreFromText(BuildContext context, String text) async { // Check if backup is encrypted diff --git a/lib/data/provider/server.freezed.dart b/lib/data/provider/server.freezed.dart index 043e6c70..e6f64aad 100644 --- a/lib/data/provider/server.freezed.dart +++ b/lib/data/provider/server.freezed.dart @@ -14,8 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$ServersState { - Map get servers;// Only store server configuration information - List get serverOrder; Set get tags; Set get manualDisconnectedIds; Timer? get autoRefreshTimer; + Map get servers; List get serverOrder; Set get tags; Set get manualDisconnectedIds; Timer? get autoRefreshTimer; /// Create a copy of ServersState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -221,9 +220,7 @@ class _ServersState implements ServersState { return EqualUnmodifiableMapView(_servers); } -// Only store server configuration information final List _serverOrder; -// Only store server configuration information @override@JsonKey() List get serverOrder { if (_serverOrder is EqualUnmodifiableListView) return _serverOrder; // ignore: implicit_dynamic_type diff --git a/lib/data/res/github_id.dart b/lib/data/res/github_id.dart index ed42a9f4..04c72827 100644 --- a/lib/data/res/github_id.dart +++ b/lib/data/res/github_id.dart @@ -124,6 +124,7 @@ abstract final class GithubIds { 'CreeperKong', 'zxf945', 'cnen2018', + 'xiaomeng9597', }; } diff --git a/lib/view/page/backup.dart b/lib/view/page/backup.dart index 67a630bb..ee71a2d0 100644 --- a/lib/view/page/backup.dart +++ b/lib/view/page/backup.dart @@ -131,14 +131,6 @@ final class _BackupPageState extends ConsumerState with AutomaticKee } } - Future _ensureBakPwd(BuildContext context) async { - final saved = await SecureStoreProps.bakPwd.read(); - if (saved != null && saved.isNotEmpty) return true; - await _onTapSetBakPwd(context); - final after = await SecureStoreProps.bakPwd.read(); - return after != null && after.isNotEmpty; - } - Widget get _buildTip { return CardX( child: ListTile( @@ -415,6 +407,11 @@ final class _BackupPageState extends ConsumerState with AutomaticKee ).cardx; } + @override + bool get wantKeepAlive => true; +} + +extension on _BackupPageState { Future _onTapWebdavDl(BuildContext context) async { webdavLoading.value = true; try { @@ -443,7 +440,7 @@ final class _BackupPageState extends ConsumerState with AutomaticKee final ok = await _ensureBakPwd(context); if (!ok) return; final savedPassword = await SecureStoreProps.bakPwd.read(); - await BackupV2.backup(bakName, savedPassword); + await BackupV2.backup(bakName, savedPassword?.isEmpty == true ? null : savedPassword); await Webdav.shared.upload(relativePath: bakName); Loggers.app.info('Upload webdav backup success'); } catch (e, s) { @@ -482,7 +479,7 @@ final class _BackupPageState extends ConsumerState with AutomaticKee final ok = await _ensureBakPwd(context); if (!ok) return; final savedPassword = await SecureStoreProps.bakPwd.read(); - await BackupV2.backup(bakName, savedPassword); + await BackupV2.backup(bakName, savedPassword?.isEmpty == true ? null : savedPassword); await GistRs.shared.upload(relativePath: bakName); Loggers.app.info('Upload gist backup success'); } catch (e, s) { @@ -624,6 +621,29 @@ final class _BackupPageState extends ConsumerState with AutomaticKee } } - @override - bool get wantKeepAlive => true; + Future _ensureBakPwd(BuildContext context) async { + final saved = await SecureStoreProps.bakPwd.read(); + if (saved != null && saved.isNotEmpty) return true; + + // Show dialog asking if user wants to set password or continue without + final result = await context.showRoundDialog( + title: l10n.backupPassword, + child: Text(l10n.backupPasswordTip, style: UIs.textGrey), + actions: [ + TextButton(onPressed: () => context.pop(true), child: Text(libL10n.cancel)), + TextButton(onPressed: () => context.pop(false), child: Text(libL10n.setting)), + ], + ); + + if (result == true) { + // Continue without password + return true; + } else if (result == false) { + // User wants to set password + await _onTapSetBakPwd(context); + return true; // Allow continuing even if password setting was cancelled + } + + return false; // User cancelled the dialog + } }