option to restore from backup during onboarding
This commit is contained in:
+18
-11
@@ -33,7 +33,20 @@ final _filenamePattern = RegExp(
|
||||
);
|
||||
|
||||
class ProfileBackupListScreen extends HookConsumerWidget {
|
||||
const ProfileBackupListScreen({super.key});
|
||||
final void Function(BuildContext context, Uri backupFileUri)?
|
||||
onBackupSelected;
|
||||
|
||||
const ProfileBackupListScreen({super.key, this.onBackupSelected});
|
||||
|
||||
Future<void> _handleSelection(BuildContext context, Uri backupFileUri) async {
|
||||
if (onBackupSelected != null) {
|
||||
onBackupSelected!(context, backupFileUri);
|
||||
} else {
|
||||
await RestoreProfileRoute(
|
||||
backupFileUri: backupFileUri.toString(),
|
||||
).push(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickDirectory(WidgetRef ref) async {
|
||||
final dir = await SafUtil().pickDirectory(
|
||||
@@ -128,21 +141,15 @@ class ProfileBackupListScreen extends HookConsumerWidget {
|
||||
.read(formatProvider.notifier)
|
||||
.fullDateTime(dateTime),
|
||||
),
|
||||
onTap: () async {
|
||||
await RestoreProfileRoute(
|
||||
backupFileUri: file.uri,
|
||||
).push(context);
|
||||
},
|
||||
onTap: () =>
|
||||
_handleSelection(context, Uri.parse(file.uri)),
|
||||
);
|
||||
} else {
|
||||
return ListTile(
|
||||
key: ValueKey(file.uri),
|
||||
title: Text(file.name),
|
||||
onTap: () async {
|
||||
await RestoreProfileRoute(
|
||||
backupFileUri: file.uri,
|
||||
).push(context);
|
||||
},
|
||||
onTap: () =>
|
||||
_handleSelection(context, Uri.parse(file.uri)),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/domain/entities/profile.dart';
|
||||
import 'package:weblibre/features/user/domain/presentation/dialogs/override_profile_dialog.dart';
|
||||
import 'package:weblibre/features/user/domain/services/user_backup.dart';
|
||||
import 'package:weblibre/utils/form_validators.dart';
|
||||
@@ -31,8 +32,16 @@ enum RestoreTarget { createOrOverride, createNew }
|
||||
|
||||
class ProfileRestoreScreen extends HookConsumerWidget {
|
||||
final Uri backupFileUri;
|
||||
final RestoreTarget? forcedTarget;
|
||||
final void Function(BuildContext context, Profile? restoredProfile)?
|
||||
onRestoreSuccess;
|
||||
|
||||
const ProfileRestoreScreen({super.key, required this.backupFileUri});
|
||||
const ProfileRestoreScreen({
|
||||
super.key,
|
||||
required this.backupFileUri,
|
||||
this.forcedTarget,
|
||||
this.onRestoreSuccess,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -41,10 +50,10 @@ class ProfileRestoreScreen extends HookConsumerWidget {
|
||||
final passwordTextController = useTextEditingController();
|
||||
final nameTextController = useTextEditingController();
|
||||
|
||||
final restoreFuture = useState<Future<bool>?>(null);
|
||||
final restoreFuture = useState<Future<Profile?>?>(null);
|
||||
final restoreState = useFuture(restoreFuture.value);
|
||||
|
||||
final restoreTarget = useState(RestoreTarget.createNew);
|
||||
final restoreTarget = useState(forcedTarget ?? RestoreTarget.createNew);
|
||||
|
||||
useEffect(() {
|
||||
if (restoreState.hasError) {
|
||||
@@ -54,7 +63,11 @@ class ProfileRestoreScreen extends HookConsumerWidget {
|
||||
} else if (restoreState.hasData) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showInfoMessage(context, 'Backup restored successfully');
|
||||
ProfileListRoute().go(context);
|
||||
if (onRestoreSuccess != null) {
|
||||
onRestoreSuccess!(context, restoreState.data);
|
||||
} else {
|
||||
ProfileListRoute().go(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,32 +106,33 @@ class ProfileRestoreScreen extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
RadioGroup(
|
||||
groupValue: restoreTarget.value,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
restoreTarget.value = value;
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
RadioListTile(
|
||||
enabled: !disableInteraction,
|
||||
value: RestoreTarget.createNew,
|
||||
title: const Text('Create New User'),
|
||||
subtitle: const Text('Restore backup as a new user'),
|
||||
),
|
||||
RadioListTile(
|
||||
enabled: !disableInteraction,
|
||||
value: RestoreTarget.createOrOverride,
|
||||
title: const Text('Restore & Replace'),
|
||||
subtitle: const Text(
|
||||
'Restore backup and overwrite existing user if present',
|
||||
if (forcedTarget == null)
|
||||
RadioGroup(
|
||||
groupValue: restoreTarget.value,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
restoreTarget.value = value;
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
RadioListTile(
|
||||
enabled: !disableInteraction,
|
||||
value: RestoreTarget.createNew,
|
||||
title: const Text('Create New User'),
|
||||
subtitle: const Text('Restore backup as a new user'),
|
||||
),
|
||||
),
|
||||
],
|
||||
RadioListTile(
|
||||
enabled: !disableInteraction,
|
||||
value: RestoreTarget.createOrOverride,
|
||||
title: const Text('Restore & Replace'),
|
||||
subtitle: const Text(
|
||||
'Restore backup and overwrite existing user if present',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (restoreTarget.value == RestoreTarget.createNew)
|
||||
TextFormField(
|
||||
controller: nameTextController,
|
||||
@@ -156,7 +170,8 @@ class ProfileRestoreScreen extends HookConsumerWidget {
|
||||
throw Exception('Override failed');
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.then<Profile?>((_) => null),
|
||||
RestoreTarget.createNew =>
|
||||
ref
|
||||
.read(userBackupServiceProvider.notifier)
|
||||
|
||||
@@ -202,7 +202,7 @@ class UserBackupService extends _$UserBackupService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> restoreAndCreateNew(
|
||||
Future<Profile> restoreAndCreateNew(
|
||||
Uri backupFileUri, {
|
||||
required String profileName,
|
||||
required String password,
|
||||
@@ -222,17 +222,18 @@ class UserBackupService extends _$UserBackupService {
|
||||
outputDirectory: outputDirectory,
|
||||
argon2Params: Argon2Params.memoryConstrained(),
|
||||
);
|
||||
await backup.unpack(password).then((_) async {
|
||||
final newProfile = await backup.unpack(password).then((_) async {
|
||||
final newProfile = Profile.create(name: profileName);
|
||||
final newPath = filesystem.getProfileDir(newProfile.uuidValue);
|
||||
|
||||
await outputDirectory.rename(newPath.path);
|
||||
await filesystem.updateProfileMetadata(newProfile);
|
||||
await filesystem.healProfile(newPath);
|
||||
return newProfile;
|
||||
});
|
||||
|
||||
ref.invalidate(profileRepositoryProvider);
|
||||
return true;
|
||||
return newProfile;
|
||||
} finally {
|
||||
try {
|
||||
if (await tempFile.exists()) {
|
||||
|
||||
@@ -41,7 +41,7 @@ final class UserBackupServiceProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$userBackupServiceHash() => r'3746ae59d490e25a44815c125f30cd0981de555d';
|
||||
String _$userBackupServiceHash() => r'52c7d3df3ce2d603692a035c77987727b840a359';
|
||||
|
||||
abstract class _$UserBackupService extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
Reference in New Issue
Block a user