diff --git a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart index 713544eb..c111baa4 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart @@ -60,17 +60,18 @@ mixin VolumeApi on GraphQLApiMap { Future> migrateToBinds( final Map serviceToDisk, + final String fallbackDrive, ) async { GenericResult? mutation; try { final GraphQLClient client = await getClient(); final input = Input$MigrateToBindsInput( - bitwardenBlockDevice: serviceToDisk['bitwarden']!, - emailBlockDevice: serviceToDisk['mailserver']!, - giteaBlockDevice: serviceToDisk['gitea']!, - nextcloudBlockDevice: serviceToDisk['nextcloud']!, - pleromaBlockDevice: serviceToDisk['pleroma']!, + bitwardenBlockDevice: serviceToDisk['bitwarden'] ?? fallbackDrive, + emailBlockDevice: serviceToDisk['email'] ?? fallbackDrive, + giteaBlockDevice: serviceToDisk['gitea'] ?? fallbackDrive, + nextcloudBlockDevice: serviceToDisk['nextcloud'] ?? fallbackDrive, + pleromaBlockDevice: serviceToDisk['pleroma'] ?? fallbackDrive, ); final variables = Variables$Mutation$MigrateToBinds(input: input); final migrateMutation = diff --git a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart index 43b415e6..3ae45aa9 100644 --- a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -68,6 +69,25 @@ class ServerJobsBloc extends Bloc { await getIt().removeAllFinishedServerJobs(); } + Future migrateToBinds(final Map serviceToDisk) async { + final fallbackDrive = getIt() + .apiData + .volumes + .data + ?.where((final drive) => drive.root) + .first + .name ?? + 'sda1'; + final result = await getIt() + .api + .migrateToBinds(serviceToDisk, fallbackDrive); + if (result.data == null) { + getIt() + .showSnackBar(result.message!, behavior: SnackBarBehavior.floating); + return; + } + } + @override void onChange(final Change change) { super.onChange(change); diff --git a/lib/ui/components/list_tiles/section_title.dart b/lib/ui/components/list_tiles/section_title.dart new file mode 100644 index 00000000..f37fb09d --- /dev/null +++ b/lib/ui/components/list_tiles/section_title.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class SectionTitle extends StatelessWidget { + const SectionTitle({ + required this.title, + super.key, + }); + + final String title; + + @override + Widget build(final BuildContext context) => Padding( + padding: const EdgeInsets.all(16), + child: Text( + title, + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ); +} diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index b652b8af..9e6cae65 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:package_info/package_info.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/ui/components/list_tiles/section_title.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; @@ -220,26 +221,6 @@ class AboutApplicationPage extends StatelessWidget { } } -class SectionTitle extends StatelessWidget { - const SectionTitle({ - required this.title, - super.key, - }); - - final String title; - - @override - Widget build(final BuildContext context) => Padding( - padding: const EdgeInsets.all(16), - child: Text( - title, - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ); -} - class LinkListTile extends StatelessWidget { const LinkListTile({ required this.title, diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index 8acf16a4..cd1c6811 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -3,8 +3,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/ui/components/list_tiles/section_title.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/router/router.dart'; @RoutePage() class DeveloperSettingsPage extends StatefulWidget { @@ -23,15 +27,7 @@ class _DeveloperSettingsPageState extends State { heroTitle: 'developer_settings.title'.tr(), heroSubtitle: 'developer_settings.subtitle'.tr(), children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'developer_settings.server_setup'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ), + SectionTitle(title: 'developer_settings.server_setup'.tr()), SwitchListTile( title: Text('developer_settings.use_staging_acme'.tr()), subtitle: @@ -59,15 +55,7 @@ class _DeveloperSettingsPageState extends State { () => TlsOptions.allowCustomSshKeyDuringSetup = value, ), ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'developer_settings.routing'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ), + SectionTitle(title: 'developer_settings.routing'.tr()), ListTile( title: Text('developer_settings.reset_onboarding'.tr()), subtitle: @@ -78,15 +66,32 @@ class _DeveloperSettingsPageState extends State { .read() .turnOffOnboarding(isOnboardingShowing: true), ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'developer_settings.cubit_statuses'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), + ListTile( + title: Text('storage.start_migration_button'.tr()), + subtitle: Text('storage.data_migration_notice'.tr()), + enabled: + !context.watch().state.isOnboardingShowing, + onTap: () => context.pushRoute( + ServicesMigrationRoute( + diskStatus: context.read().state.diskStatus, + services: context + .read() + .state + .services + .where( + (final service) => + service.id == 'bitwarden' || + service.id == 'gitea' || + service.id == 'pleroma' || + service.id == 'email' || + service.id == 'nextcloud', + ) + .toList(), + isMigration: true, + ), ), ), + SectionTitle(title: 'developer_settings.cubit_statuses'.tr()), ListTile( title: const Text('ApiConnectionRepository status'), subtitle: Text( diff --git a/lib/ui/pages/server_storage/binds_migration/services_migration.dart b/lib/ui/pages/server_storage/binds_migration/services_migration.dart index 53e72d91..695b8de4 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -18,11 +18,13 @@ class ServicesMigrationPage extends StatefulWidget { const ServicesMigrationPage({ required this.services, required this.diskStatus, + required this.isMigration, super.key, }); final DiskStatus diskStatus; final List services; + final bool isMigration; @override State createState() => _ServicesMigrationPageState(); @@ -169,18 +171,24 @@ class _ServicesMigrationPageState extends State { ), ), const SizedBox(height: 16), - if (isVolumePicked) + if (widget.isMigration || (!widget.isMigration && isVolumePicked)) BrandButton.filled( child: Text('storage.start_migration_button'.tr()), onPressed: () { - for (final service in widget.services) { - if (serviceToDisk[service.id] != null) { - context.read().add( - ServiceMove( - service, - serviceToDisk[service.id]!, - ), - ); + if (widget.isMigration) { + context.read().migrateToBinds( + serviceToDisk, + ); + } else { + for (final service in widget.services) { + if (serviceToDisk[service.id] != null) { + context.read().add( + ServiceMove( + service, + serviceToDisk[service.id]!, + ), + ); + } } } context.router.popUntilRoot(); diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index 41e7f7ef..c0064eef 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -114,6 +114,7 @@ class _ServicePageState extends State { ServicesMigrationRoute( services: [service], diskStatus: context.read().state.diskStatus, + isMigration: false, ), ), leading: const Icon(Icons.drive_file_move_outlined), diff --git a/lib/ui/router/router.gr.dart b/lib/ui/router/router.gr.dart index 1ff6d09c..98f4453a 100644 --- a/lib/ui/router/router.gr.dart +++ b/lib/ui/router/router.gr.dart @@ -159,6 +159,7 @@ abstract class _$RootRouter extends RootStackRouter { child: ServicesMigrationPage( services: args.services, diskStatus: args.diskStatus, + isMigration: args.isMigration, key: args.key, ), ); @@ -575,6 +576,7 @@ class ServicesMigrationRoute extends PageRouteInfo { ServicesMigrationRoute({ required List services, required DiskStatus diskStatus, + required bool isMigration, Key? key, List? children, }) : super( @@ -582,6 +584,7 @@ class ServicesMigrationRoute extends PageRouteInfo { args: ServicesMigrationRouteArgs( services: services, diskStatus: diskStatus, + isMigration: isMigration, key: key, ), initialChildren: children, @@ -597,6 +600,7 @@ class ServicesMigrationRouteArgs { const ServicesMigrationRouteArgs({ required this.services, required this.diskStatus, + required this.isMigration, this.key, }); @@ -604,11 +608,13 @@ class ServicesMigrationRouteArgs { final DiskStatus diskStatus; + final bool isMigration; + final Key? key; @override String toString() { - return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, key: $key}'; + return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}'; } }