mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
opt.: bak pwd is optional (#872)
This commit is contained in:
@@ -14,11 +14,7 @@ final class BakSyncer extends SyncIface {
|
||||
@override
|
||||
Future<void> 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
|
||||
|
||||
@@ -11,27 +11,10 @@ class BackupService {
|
||||
/// Perform backup operation with the given source
|
||||
static Future<void> 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<String?> _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<bool>(
|
||||
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<void> restoreFromText(BuildContext context, String text) async {
|
||||
// Check if backup is encrypted
|
||||
|
||||
@@ -14,8 +14,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$ServersState {
|
||||
|
||||
Map<String, Spi> get servers;// Only store server configuration information
|
||||
List<String> get serverOrder; Set<String> get tags; Set<String> get manualDisconnectedIds; Timer? get autoRefreshTimer;
|
||||
Map<String, Spi> get servers; List<String> get serverOrder; Set<String> get tags; Set<String> 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<String> _serverOrder;
|
||||
// Only store server configuration information
|
||||
@override@JsonKey() List<String> get serverOrder {
|
||||
if (_serverOrder is EqualUnmodifiableListView) return _serverOrder;
|
||||
// ignore: implicit_dynamic_type
|
||||
|
||||
@@ -124,6 +124,7 @@ abstract final class GithubIds {
|
||||
'CreeperKong',
|
||||
'zxf945',
|
||||
'cnen2018',
|
||||
'xiaomeng9597',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -131,14 +131,6 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _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<BackupPage> with AutomaticKee
|
||||
).cardx;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
extension on _BackupPageState {
|
||||
Future<void> _onTapWebdavDl(BuildContext context) async {
|
||||
webdavLoading.value = true;
|
||||
try {
|
||||
@@ -443,7 +440,7 @@ final class _BackupPageState extends ConsumerState<BackupPage> 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<BackupPage> 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<BackupPage> with AutomaticKee
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
Future<bool> _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<bool>(
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user