From b19f9c40ef93cb7822225a238dee0332d24ce69d Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Jun 2025 17:46:24 -0500 Subject: [PATCH] wip --- mobile/lib/domain/models/store.model.dart | 3 +- .../backup/backup_album_selection.page.dart | 248 ++++++----- .../pages/backup/backup_controller.page.dart | 60 +-- .../exp_backup_album_selection.page.dart | 304 ++++++++++++++ .../backup/exp_backup_controller.page.dart | 395 ++++++++++++++++++ mobile/lib/routing/router.dart | 6 + mobile/lib/routing/router.gr.dart | 32 ++ .../lib/widgets/backup/album_info_card.dart | 43 +- .../widgets/backup/album_info_list_tile.dart | 33 +- .../backup/exp_album_info_list_tile.dart | 122 ++++++ .../backup/exp_upload_option_toggle.dart | 61 +++ mobile/lib/widgets/common/immich_app_bar.dart | 9 +- 12 files changed, 1112 insertions(+), 204 deletions(-) create mode 100644 mobile/lib/pages/backup/exp_backup_album_selection.page.dart create mode 100644 mobile/lib/pages/backup/exp_backup_controller.page.dart create mode 100644 mobile/lib/widgets/backup/exp_album_info_list_tile.dart create mode 100644 mobile/lib/widgets/backup/exp_upload_option_toggle.dart diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index 8a5a908e0de..427e4ed0d1b 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -68,7 +68,8 @@ enum StoreKey { manageLocalMediaAndroid._(137), // Experimental stuff - photoManagerCustomFilter._(1000); + photoManagerCustomFilter._(1000), + newUpload._(1001); const StoreKey._(this.id); final int id; diff --git a/mobile/lib/pages/backup/backup_album_selection.page.dart b/mobile/lib/pages/backup/backup_album_selection.page.dart index 5082e30608b..c4124efb525 100644 --- a/mobile/lib/pages/backup/backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/backup_album_selection.page.dart @@ -3,11 +3,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/local_album.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/widgets/backup/album_info_card.dart'; @@ -19,17 +17,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { const BackupAlbumSelectionPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final albums = ref.watch(backupAlbumProvider); - - final selectedBackupAlbums = albums - .where((album) => album.backupSelection == BackupSelection.selected) - .toList(); - final excludedBackupAlbums = albums - .where((album) => album.backupSelection == BackupSelection.excluded) - .toList(); + final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums; + final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums; final enableSyncUploadAlbum = useAppSettingsState(AppSettingsEnum.syncAlbums); final isDarkTheme = context.isDarkTheme; + final albums = ref.watch(backupProvider).availableAlbums; useEffect( () { @@ -92,9 +85,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { buildSelectedAlbumNameChip() { return selectedBackupAlbums.map((album) { - void removeSelection() { - ref.read(backupAlbumProvider.notifier).deselectAlbum(album); - } + void removeSelection() => + ref.read(backupProvider.notifier).removeAlbumForBackup(album); return Padding( padding: const EdgeInsets.only(right: 8.0), @@ -125,7 +117,9 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { buildExcludedAlbumNameChip() { return excludedBackupAlbums.map((album) { void removeSelection() { - ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + ref + .watch(backupProvider.notifier) + .removeExcludedAlbumForBackup(album); } return GestureDetector( @@ -174,131 +168,129 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { ).tr(), elevation: 0, ), - body: SafeArea( - child: CustomScrollView( - physics: const ClampingScrollPhysics(), - slivers: [ - SliverToBoxAdapter( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, - horizontal: 16.0, - ), - child: Text( - "backup_album_selection_page_selection_info", - style: context.textTheme.titleSmall, - ).tr(), + body: CustomScrollView( + physics: const ClampingScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 16.0, ), - // Selected Album Chips + child: Text( + "backup_album_selection_page_selection_info", + style: context.textTheme.titleSmall, + ).tr(), + ), + // Selected Album Chips - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Wrap( - children: [ - ...buildSelectedAlbumNameChip(), - ...buildExcludedAlbumNameChip(), - ], - ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Wrap( + children: [ + ...buildSelectedAlbumNameChip(), + ...buildExcludedAlbumNameChip(), + ], ), + ), - SettingsSwitchListTile( - valueNotifier: enableSyncUploadAlbum, - title: "sync_albums".tr(), - subtitle: "sync_upload_album_setting_subtitle".tr(), - contentPadding: const EdgeInsets.symmetric(horizontal: 16), - titleStyle: context.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - subtitleStyle: context.textTheme.labelLarge?.copyWith( - color: context.colorScheme.primary, - ), - onChanged: handleSyncAlbumToggle, + SettingsSwitchListTile( + valueNotifier: enableSyncUploadAlbum, + title: "sync_albums".tr(), + subtitle: "sync_upload_album_setting_subtitle".tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + titleStyle: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, ), + subtitleStyle: context.textTheme.labelLarge?.copyWith( + color: context.colorScheme.primary, + ), + onChanged: handleSyncAlbumToggle, + ), - ListTile( - title: Text( - "backup_album_selection_page_albums_device".tr( - namedArgs: { - 'count': ref - .watch(backupProvider) - .availableAlbums - .length - .toString(), - }, - ), - style: context.textTheme.titleSmall, - ), - subtitle: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text( - "backup_album_selection_page_albums_tap", - style: context.textTheme.labelLarge?.copyWith( - color: context.primaryColor, - ), - ).tr(), - ), - trailing: IconButton( - splashRadius: 16, - icon: Icon( - Icons.info, - size: 20, - color: context.primaryColor, - ), - onPressed: () { - // show the dialog - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - elevation: 5, - title: Text( - 'backup_album_selection_page_selection_info', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: context.primaryColor, - ), - ).tr(), - content: SingleChildScrollView( - child: ListBody( - children: [ - const Text( - 'backup_album_selection_page_assets_scatter', - style: TextStyle( - fontSize: 14, - ), - ).tr(), - ], - ), - ), - ); - }, - ); + ListTile( + title: Text( + "backup_album_selection_page_albums_device".tr( + namedArgs: { + 'count': ref + .watch(backupProvider) + .availableAlbums + .length + .toString(), }, ), + style: context.textTheme.titleSmall, ), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + "backup_album_selection_page_albums_tap", + style: context.textTheme.labelLarge?.copyWith( + color: context.primaryColor, + ), + ).tr(), + ), + trailing: IconButton( + splashRadius: 16, + icon: Icon( + Icons.info, + size: 20, + color: context.primaryColor, + ), + onPressed: () { + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 5, + title: Text( + 'backup_album_selection_page_selection_info', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: context.primaryColor, + ), + ).tr(), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text( + 'backup_album_selection_page_assets_scatter', + style: TextStyle( + fontSize: 14, + ), + ).tr(), + ], + ), + ), + ); + }, + ); + }, + ), + ), - // buildSearchBar(), - ], - ), + // buildSearchBar(), + ], ), - SliverLayoutBuilder( - builder: (context, constraints) { - if (constraints.crossAxisExtent > 600) { - return buildAlbumSelectionGrid(); - } else { - return buildAlbumSelectionList(); - } - }, - ), - ], - ), + ), + SliverLayoutBuilder( + builder: (context, constraints) { + if (constraints.crossAxisExtent > 600) { + return buildAlbumSelectionGrid(); + } else { + return buildAlbumSelectionList(); + } + }, + ), + ], ), ); } diff --git a/mobile/lib/pages/backup/backup_controller.page.dart b/mobile/lib/pages/backup/backup_controller.page.dart index 20407f397db..474f880874b 100644 --- a/mobile/lib/pages/backup/backup_controller.page.dart +++ b/mobile/lib/pages/backup/backup_controller.page.dart @@ -6,21 +6,19 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/local_album.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart'; import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/upload.service.dart'; import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart'; +import 'package:immich_mobile/widgets/backup/exp_upload_option_toggle.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @RoutePage() @@ -88,13 +86,8 @@ class BackupControllerPage extends HookConsumerWidget { ); Widget buildSelectedAlbumName() { - String text = "backup_controller_page_backup_selected".tr(); - final albums = ref - .watch(backupAlbumProvider) - .where( - (album) => album.backupSelection == BackupSelection.selected, - ) - .toList(); + var text = "backup_controller_page_backup_selected".tr(); + var albums = ref.watch(backupProvider).selectedBackupAlbums; if (albums.isNotEmpty) { for (var album in albums) { @@ -128,13 +121,8 @@ class BackupControllerPage extends HookConsumerWidget { } Widget buildExcludedAlbumName() { - String text = "backup_controller_page_excluded".tr(); - final albums = ref - .watch(backupAlbumProvider) - .where( - (album) => album.backupSelection == BackupSelection.excluded, - ) - .toList(); + var text = "backup_controller_page_excluded".tr(); + var albums = ref.watch(backupProvider).excludedBackupAlbums; if (albums.isNotEmpty) { for (var album in albums) { @@ -315,11 +303,19 @@ class BackupControllerPage extends HookConsumerWidget { body: Stack( children: [ Padding( - padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32), + padding: const EdgeInsets.only( + left: 16.0, + right: 16, + bottom: 32, + ), child: ListView( - // crossAxisAlignment: CrossAxisAlignment.start, children: hasAnyAlbum ? [ + const SizedBox(height: 8), + ExpUploadOptionToggle( + onToggle: () => + context.replaceRoute(const ExpBackupRoute()), + ), buildFolderSelectionTile(), BackupInfoCard( title: "total".tr(), @@ -346,32 +342,6 @@ class BackupControllerPage extends HookConsumerWidget { const CurrentUploadingAssetInfoBox(), if (!hasExclusiveAccess) buildBackgroundBackupInfo(), buildBackupButton(), - ElevatedButton( - onPressed: () { - ref.watch(uploadServiceProvider).getRecords(); - }, - child: const Text( - "get record", - ), - ), - ElevatedButton( - onPressed: () { - ref - .watch(uploadServiceProvider) - .deleteAllUploadTasks(); - }, - child: const Text( - "clear records", - ), - ), - ElevatedButton( - onPressed: () { - ref.watch(uploadServiceProvider).cancelAllUpload(); - }, - child: const Text( - "cancel all uploads", - ), - ), ] : [ buildFolderSelectionTile(), diff --git a/mobile/lib/pages/backup/exp_backup_album_selection.page.dart b/mobile/lib/pages/backup/exp_backup_album_selection.page.dart new file mode 100644 index 00000000000..69c032a212c --- /dev/null +++ b/mobile/lib/pages/backup/exp_backup_album_selection.page.dart @@ -0,0 +1,304 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/local_album.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/widgets/backup/exp_album_info_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; + +@RoutePage() +class ExpBackupAlbumSelectionPage extends HookConsumerWidget { + const ExpBackupAlbumSelectionPage({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final albums = ref.watch(backupAlbumProvider); + + final selectedBackupAlbums = albums + .where((album) => album.backupSelection == BackupSelection.selected) + .toList(); + final excludedBackupAlbums = albums + .where((album) => album.backupSelection == BackupSelection.excluded) + .toList(); + final enableSyncUploadAlbum = + useAppSettingsState(AppSettingsEnum.syncAlbums); + final isDarkTheme = context.isDarkTheme; + + useEffect( + () { + ref.watch(backupProvider.notifier).getBackupInfo(); + return null; + }, + [], + ); + + buildAlbumSelectionList() { + if (albums.isEmpty) { + return const SliverToBoxAdapter( + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + + return SliverPadding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + ((context, index) { + return ExpAlbumInfoListTile( + album: albums[index], + ); + }), + childCount: albums.length, + ), + ), + ); + } + + buildAlbumSelectionGrid() { + if (albums.isEmpty) { + return const SliverToBoxAdapter( + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + + return SliverPadding( + padding: const EdgeInsets.all(12.0), + sliver: SliverGrid.builder( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 300, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + ), + itemCount: albums.length, + itemBuilder: ((context, index) { + return ExpAlbumInfoListTile( + album: albums[index], + ); + }), + ), + ); + } + + buildSelectedAlbumNameChip() { + return selectedBackupAlbums.map((album) { + void removeSelection() { + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + } + + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: GestureDetector( + onTap: removeSelection, + child: Chip( + label: Text( + album.name, + style: TextStyle( + fontSize: 12, + color: isDarkTheme ? Colors.black : Colors.white, + fontWeight: FontWeight.bold, + ), + ), + backgroundColor: context.primaryColor, + deleteIconColor: isDarkTheme ? Colors.black : Colors.white, + deleteIcon: const Icon( + Icons.cancel_rounded, + size: 15, + ), + onDeleted: removeSelection, + ), + ), + ); + }).toSet(); + } + + buildExcludedAlbumNameChip() { + return excludedBackupAlbums.map((album) { + void removeSelection() { + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + } + + return GestureDetector( + onTap: removeSelection, + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Chip( + label: Text( + album.name, + style: TextStyle( + fontSize: 12, + color: context.scaffoldBackgroundColor, + fontWeight: FontWeight.bold, + ), + ), + backgroundColor: Colors.red[300], + deleteIconColor: context.scaffoldBackgroundColor, + deleteIcon: const Icon( + Icons.cancel_rounded, + size: 15, + ), + onDeleted: removeSelection, + ), + ), + ); + }).toSet(); + } + + handleSyncAlbumToggle(bool isEnable) async { + if (isEnable) { + await ref.read(albumProvider.notifier).refreshRemoteAlbums(); + for (final album in selectedBackupAlbums) { + await ref.read(albumProvider.notifier).createSyncAlbum(album.name); + } + } + } + + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => context.maybePop(), + icon: const Icon(Icons.arrow_back_ios_rounded), + ), + title: const Text( + "backup_album_selection_page_select_albums", + ).tr(), + elevation: 0, + ), + body: SafeArea( + child: CustomScrollView( + physics: const ClampingScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 16.0, + ), + child: Text( + "backup_album_selection_page_selection_info", + style: context.textTheme.titleSmall, + ).tr(), + ), + // Selected Album Chips + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Wrap( + children: [ + ...buildSelectedAlbumNameChip(), + ...buildExcludedAlbumNameChip(), + ], + ), + ), + + SettingsSwitchListTile( + valueNotifier: enableSyncUploadAlbum, + title: "sync_albums".tr(), + subtitle: "sync_upload_album_setting_subtitle".tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + titleStyle: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + subtitleStyle: context.textTheme.labelLarge?.copyWith( + color: context.colorScheme.primary, + ), + onChanged: handleSyncAlbumToggle, + ), + + ListTile( + title: Text( + "backup_album_selection_page_albums_device".tr( + namedArgs: { + 'count': ref + .watch(backupProvider) + .availableAlbums + .length + .toString(), + }, + ), + style: context.textTheme.titleSmall, + ), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + "backup_album_selection_page_albums_tap", + style: context.textTheme.labelLarge?.copyWith( + color: context.primaryColor, + ), + ).tr(), + ), + trailing: IconButton( + splashRadius: 16, + icon: Icon( + Icons.info, + size: 20, + color: context.primaryColor, + ), + onPressed: () { + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 5, + title: Text( + 'backup_album_selection_page_selection_info', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: context.primaryColor, + ), + ).tr(), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text( + 'backup_album_selection_page_assets_scatter', + style: TextStyle( + fontSize: 14, + ), + ).tr(), + ], + ), + ), + ); + }, + ); + }, + ), + ), + + // buildSearchBar(), + ], + ), + ), + SliverLayoutBuilder( + builder: (context, constraints) { + if (constraints.crossAxisExtent > 600) { + return buildAlbumSelectionGrid(); + } else { + return buildAlbumSelectionList(); + } + }, + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/pages/backup/exp_backup_controller.page.dart b/mobile/lib/pages/backup/exp_backup_controller.page.dart new file mode 100644 index 00000000000..e058d8ef05f --- /dev/null +++ b/mobile/lib/pages/backup/exp_backup_controller.page.dart @@ -0,0 +1,395 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/local_album.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; +import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/upload.service.dart'; +import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; +import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart'; +import 'package:immich_mobile/widgets/backup/exp_upload_option_toggle.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +@RoutePage() +class ExpBackupPage extends HookConsumerWidget { + const ExpBackupPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + BackUpState backupState = ref.watch(backupProvider); + final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty; + final didGetBackupInfo = useState(false); + + bool hasExclusiveAccess = + backupState.backupProgress != BackUpProgressEnum.inBackground; + bool shouldBackup = backupState.allUniqueAssets.length - + backupState.selectedAlbumsBackupAssetsIds.length == + 0 || + !hasExclusiveAccess + ? false + : true; + + useEffect( + () { + // Update the background settings information just to make sure we + // have the latest, since the platform channel will not update + // automatically + if (Platform.isIOS) { + ref.watch(iOSBackgroundSettingsProvider.notifier).refresh(); + } + + ref + .watch(websocketProvider.notifier) + .stopListenToEvent('on_upload_success'); + + return () { + WakelockPlus.disable(); + }; + }, + [], + ); + + useEffect( + () { + if (backupState.backupProgress == BackUpProgressEnum.idle && + !didGetBackupInfo.value) { + ref.watch(backupProvider.notifier).getBackupInfo(); + didGetBackupInfo.value = true; + } + return null; + }, + [backupState.backupProgress], + ); + + useEffect( + () { + if (backupState.backupProgress == BackUpProgressEnum.inProgress) { + WakelockPlus.enable(); + } else { + WakelockPlus.disable(); + } + + return null; + }, + [backupState.backupProgress], + ); + + Widget buildSelectedAlbumName() { + String text = "backup_controller_page_backup_selected".tr(); + final albums = ref + .watch(backupAlbumProvider) + .where( + (album) => album.backupSelection == BackupSelection.selected, + ) + .toList(); + + if (albums.isNotEmpty) { + for (var album in albums) { + if (album.name == "Recent" || album.name == "Recents") { + text += "${album.name} (${'all'.tr()}), "; + } else { + text += "${album.name}, "; + } + } + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + text.trim().substring(0, text.length - 2), + style: context.textTheme.labelLarge?.copyWith( + color: context.primaryColor, + ), + ), + ); + } else { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + "backup_controller_page_none_selected".tr(), + style: context.textTheme.labelLarge?.copyWith( + color: context.primaryColor, + ), + ), + ); + } + } + + Widget buildExcludedAlbumName() { + String text = "backup_controller_page_excluded".tr(); + final albums = ref + .watch(backupAlbumProvider) + .where( + (album) => album.backupSelection == BackupSelection.excluded, + ) + .toList(); + + if (albums.isNotEmpty) { + for (var album in albums) { + text += "${album.name}, "; + } + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + text.trim().substring(0, text.length - 2), + style: context.textTheme.labelLarge?.copyWith( + color: Colors.red[300], + ), + ), + ); + } else { + return const SizedBox(); + } + } + + buildFolderSelectionTile() { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: BorderSide( + color: context.colorScheme.outlineVariant, + width: 1, + ), + ), + elevation: 0, + borderOnForeground: false, + child: ListTile( + minVerticalPadding: 18, + title: Text( + "backup_controller_page_albums", + style: context.textTheme.titleMedium, + ).tr(), + subtitle: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "backup_controller_page_to_backup", + style: context.textTheme.bodyMedium?.copyWith( + color: context.colorScheme.onSurfaceSecondary, + ), + ).tr(), + buildSelectedAlbumName(), + buildExcludedAlbumName(), + ], + ), + ), + trailing: ElevatedButton( + onPressed: () async { + await context.pushRoute(const BackupAlbumSelectionRoute()); + // waited until returning from selection + await ref + .read(backupProvider.notifier) + .backupAlbumSelectionDone(); + // waited until backup albums are stored in DB + ref.read(albumProvider.notifier).refreshDeviceAlbums(); + }, + child: const Text( + "select", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ).tr(), + ), + ), + ), + ); + } + + void startBackup() { + ref.watch(errorBackupListProvider.notifier).empty(); + if (ref.watch(backupProvider).backupProgress != + BackUpProgressEnum.inBackground) { + ref.watch(backupProvider.notifier).startBackupProcess(); + } + } + + Widget buildBackupButton() { + return Padding( + padding: const EdgeInsets.only( + top: 24, + ), + child: Container( + child: backupState.backupProgress == BackUpProgressEnum.inProgress || + backupState.backupProgress == + BackUpProgressEnum.manualInProgress + ? ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.grey[50], + backgroundColor: Colors.red[300], + // padding: const EdgeInsets.all(14), + ), + onPressed: () { + if (backupState.backupProgress == + BackUpProgressEnum.manualInProgress) { + ref.read(manualUploadProvider.notifier).cancelBackup(); + } else { + ref.read(backupProvider.notifier).cancelBackup(); + } + }, + child: const Text( + "cancel", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ).tr(), + ) + : ElevatedButton( + onPressed: shouldBackup ? startBackup : null, + child: const Text( + "backup_controller_page_start_backup", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ).tr(), + ), + ), + ); + } + + buildBackgroundBackupInfo() { + return const ListTile( + leading: Icon(Icons.info_outline_rounded), + title: Text( + "Background backup is currently running, cannot start manual backup", + ), + ); + } + + buildLoadingIndicator() { + return const Padding( + padding: EdgeInsets.only(top: 42.0), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + + return Scaffold( + appBar: AppBar( + elevation: 0, + title: const Text( + "Backup (Experimental)", + ), + leading: IconButton( + onPressed: () { + ref.watch(websocketProvider.notifier).listenUploadEvent(); + context.maybePop(true); + }, + splashRadius: 24, + icon: const Icon( + Icons.arrow_back_ios_rounded, + ), + ), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: IconButton( + onPressed: () => context.pushRoute(const BackupOptionsRoute()), + splashRadius: 24, + icon: const Icon( + Icons.settings_outlined, + ), + ), + ), + ], + ), + body: Stack( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 16.0, + right: 16, + bottom: 32, + ), + child: ListView( + children: hasAnyAlbum + ? [ + const SizedBox(height: 8), + ExpUploadOptionToggle( + onToggle: () => + context.replaceRoute(const BackupControllerRoute()), + ), + buildFolderSelectionTile(), + BackupInfoCard( + title: "total".tr(), + subtitle: "backup_controller_page_total_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${backupState.allUniqueAssets.length}", + ), + BackupInfoCard( + title: "backup_controller_page_backup".tr(), + subtitle: "backup_controller_page_backup_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${backupState.selectedAlbumsBackupAssetsIds.length}", + ), + BackupInfoCard( + title: "backup_controller_page_remainder".tr(), + subtitle: "backup_controller_page_remainder_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${max(0, backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length)}", + ), + const Divider(), + const CurrentUploadingAssetInfoBox(), + if (!hasExclusiveAccess) buildBackgroundBackupInfo(), + buildBackupButton(), + ElevatedButton( + onPressed: () { + ref.watch(uploadServiceProvider).getRecords(); + }, + child: const Text( + "get record", + ), + ), + ElevatedButton( + onPressed: () { + ref + .watch(uploadServiceProvider) + .deleteAllUploadTasks(); + }, + child: const Text( + "clear records", + ), + ), + ElevatedButton( + onPressed: () { + ref.watch(uploadServiceProvider).cancelAllUpload(); + }, + child: const Text( + "cancel all uploads", + ), + ), + ] + : [ + buildFolderSelectionTile(), + if (!didGetBackupInfo.value) buildLoadingIndicator(), + ], + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 1f14aaa5bfb..7774d58d415 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -17,6 +17,8 @@ import 'package:immich_mobile/pages/album/album_shared_user_selection.page.dart' import 'package:immich_mobile/pages/album/album_viewer.page.dart'; import 'package:immich_mobile/pages/albums/albums.page.dart'; import 'package:immich_mobile/pages/backup/album_preview.page.dart'; +import 'package:immich_mobile/pages/backup/exp_backup_album_selection.page.dart'; +import 'package:immich_mobile/pages/backup/exp_backup_controller.page.dart'; import 'package:immich_mobile/pages/backup/backup_album_selection.page.dart'; import 'package:immich_mobile/pages/backup/backup_controller.page.dart'; import 'package:immich_mobile/pages/backup/backup_options.page.dart'; @@ -330,5 +332,9 @@ class AppRouter extends RootStackRouter { page: RemoteMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard], ), + AutoRoute( + page: ExpBackupRoute.page, + guards: [_authGuard, _duplicateGuard], + ), ]; } diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 0c57949f04c..81b81717626 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -608,6 +608,38 @@ class EditImageRouteArgs { } } +/// generated route for +/// [ExpBackupAlbumSelectionPage] +class ExpBackupAlbumSelectionRoute extends PageRouteInfo { + const ExpBackupAlbumSelectionRoute({List? children}) + : super(ExpBackupAlbumSelectionRoute.name, initialChildren: children); + + static const String name = 'ExpBackupAlbumSelectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ExpBackupAlbumSelectionPage(); + }, + ); +} + +/// generated route for +/// [ExpBackupPage] +class ExpBackupRoute extends PageRouteInfo { + const ExpBackupRoute({List? children}) + : super(ExpBackupRoute.name, initialChildren: children); + + static const String name = 'ExpBackupRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ExpBackupPage(); + }, + ); +} + /// generated route for /// [FailedBackupStatusPage] class FailedBackupStatusRoute extends PageRouteInfo { diff --git a/mobile/lib/widgets/backup/album_info_card.dart b/mobile/lib/widgets/backup/album_info_card.dart index f626048a395..526d2199be7 100644 --- a/mobile/lib/widgets/backup/album_info_card.dart +++ b/mobile/lib/widgets/backup/album_info_card.dart @@ -3,19 +3,18 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/local_album.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/backup/available_album.model.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; class AlbumInfoCard extends HookConsumerWidget { - final LocalAlbum album; + final AvailableAlbum album; const AlbumInfoCard({ super.key, @@ -24,8 +23,10 @@ class AlbumInfoCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final bool isSelected = album.backupSelection == BackupSelection.selected; - final bool isExcluded = album.backupSelection == BackupSelection.excluded; + final bool isSelected = + ref.watch(backupProvider).selectedBackupAlbums.contains(album); + final bool isExcluded = + ref.watch(backupProvider).excludedBackupAlbums.contains(album); final syncAlbum = ref .watch(appSettingsServiceProvider) .getSetting(AppSettingsEnum.syncAlbums); @@ -90,10 +91,9 @@ class AlbumInfoCard extends HookConsumerWidget { ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isSelected) { - ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + ref.read(backupProvider.notifier).removeAlbumForBackup(album); } else { - ref.read(backupAlbumProvider.notifier).selectAlbum(album); - + ref.read(backupProvider.notifier).addAlbumForBackup(album); if (syncAlbum) { ref.read(albumProvider.notifier).createSyncAlbum(album.name); } @@ -103,8 +103,11 @@ class AlbumInfoCard extends HookConsumerWidget { ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isExcluded) { - ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + // Remove from exclude album list + ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album); } else { + // Add to exclude album list + if (album.id == 'isAll' || album.name == 'Recents') { ImmichToast.show( context: context, @@ -114,7 +117,8 @@ class AlbumInfoCard extends HookConsumerWidget { ); return; } - ref.read(backupAlbumProvider.notifier).excludeAlbum(album); + + ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album); } }, child: Card( @@ -177,16 +181,25 @@ class AlbumInfoCard extends HookConsumerWidget { fontWeight: FontWeight.bold, ), ), + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Text( + album.assetCount.toString() + + (album.isAll ? " (${'all'.tr()})" : ""), + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ), ], ), ), IconButton( onPressed: () { - // TODO: refactor below - - // context.pushRoute( - // AlbumPreviewRoute(album: album.album), - // ); + context.pushRoute( + AlbumPreviewRoute(album: album.album), + ); }, icon: Icon( Icons.image_outlined, diff --git a/mobile/lib/widgets/backup/album_info_list_tile.dart b/mobile/lib/widgets/backup/album_info_list_tile.dart index e10cf5d49b6..a263c004bdf 100644 --- a/mobile/lib/widgets/backup/album_info_list_tile.dart +++ b/mobile/lib/widgets/backup/album_info_list_tile.dart @@ -1,25 +1,28 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/local_album.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/backup/available_album.model.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; class AlbumInfoListTile extends HookConsumerWidget { - final LocalAlbum album; + final AvailableAlbum album; const AlbumInfoListTile({super.key, required this.album}); @override Widget build(BuildContext context, WidgetRef ref) { - final bool isSelected = album.backupSelection == BackupSelection.selected; - final bool isExcluded = album.backupSelection == BackupSelection.excluded; - + final bool isSelected = + ref.watch(backupProvider).selectedBackupAlbums.contains(album); + final bool isExcluded = + ref.watch(backupProvider).excludedBackupAlbums.contains(album); final syncAlbum = ref .watch(appSettingsServiceProvider) .getSetting(AppSettingsEnum.syncAlbums); @@ -64,8 +67,11 @@ class AlbumInfoListTile extends HookConsumerWidget { ref.watch(hapticFeedbackProvider.notifier).selectionClick(); if (isExcluded) { - ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + // Remove from exclude album list + ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album); } else { + // Add to exclude album list + if (album.id == 'isAll' || album.name == 'Recents') { ImmichToast.show( context: context, @@ -76,7 +82,7 @@ class AlbumInfoListTile extends HookConsumerWidget { return; } - ref.read(backupAlbumProvider.notifier).excludeAlbum(album); + ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album); } }, child: ListTile( @@ -85,9 +91,9 @@ class AlbumInfoListTile extends HookConsumerWidget { onTap: () { ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isSelected) { - ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + ref.read(backupProvider.notifier).removeAlbumForBackup(album); } else { - ref.read(backupAlbumProvider.notifier).selectAlbum(album); + ref.read(backupProvider.notifier).addAlbumForBackup(album); if (syncAlbum) { ref.read(albumProvider.notifier).createSyncAlbum(album.name); } @@ -104,10 +110,9 @@ class AlbumInfoListTile extends HookConsumerWidget { subtitle: Text(album.assetCount.toString()), trailing: IconButton( onPressed: () { - // TODO: refactor below - // context.pushRoute( - // AlbumPreviewRoute(album: album.album), - // ); + context.pushRoute( + AlbumPreviewRoute(album: album.album), + ); }, icon: Icon( Icons.image_outlined, diff --git a/mobile/lib/widgets/backup/exp_album_info_list_tile.dart b/mobile/lib/widgets/backup/exp_album_info_list_tile.dart new file mode 100644 index 00000000000..7d8e0be39f1 --- /dev/null +++ b/mobile/lib/widgets/backup/exp_album_info_list_tile.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/local_album.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class ExpAlbumInfoListTile extends HookConsumerWidget { + final LocalAlbum album; + + const ExpAlbumInfoListTile({super.key, required this.album}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bool isSelected = album.backupSelection == BackupSelection.selected; + final bool isExcluded = album.backupSelection == BackupSelection.excluded; + + final syncAlbum = ref + .watch(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.syncAlbums); + + buildTileColor() { + if (isSelected) { + return context.isDarkTheme + ? context.primaryColor.withAlpha(100) + : context.primaryColor.withAlpha(25); + } else if (isExcluded) { + return context.isDarkTheme + ? Colors.red[300]?.withAlpha(150) + : Colors.red[100]?.withAlpha(150); + } else { + return Colors.transparent; + } + } + + buildIcon() { + if (isSelected) { + return Icon( + Icons.check_circle_rounded, + color: context.colorScheme.primary, + ); + } + + if (isExcluded) { + return Icon( + Icons.remove_circle_rounded, + color: context.colorScheme.error, + ); + } + + return Icon( + Icons.circle, + color: context.colorScheme.surfaceContainerHighest, + ); + } + + return GestureDetector( + onDoubleTap: () { + ref.watch(hapticFeedbackProvider.notifier).selectionClick(); + + if (isExcluded) { + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + } else { + if (album.id == 'isAll' || album.name == 'Recents') { + ImmichToast.show( + context: context, + msg: 'Cannot exclude album contains all assets', + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + return; + } + + ref.read(backupAlbumProvider.notifier).excludeAlbum(album); + } + }, + child: ListTile( + tileColor: buildTileColor(), + contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + onTap: () { + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + if (isSelected) { + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + } else { + ref.read(backupAlbumProvider.notifier).selectAlbum(album); + if (syncAlbum) { + ref.read(albumProvider.notifier).createSyncAlbum(album.name); + } + } + }, + leading: buildIcon(), + title: Text( + album.name, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text(album.assetCount.toString()), + trailing: IconButton( + onPressed: () { + // TODO: refactor below + // context.pushRoute( + // AlbumPreviewRoute(album: album.album), + // ); + }, + icon: Icon( + Icons.image_outlined, + color: context.primaryColor, + size: 24, + ), + splashRadius: 25, + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/exp_upload_option_toggle.dart b/mobile/lib/widgets/backup/exp_upload_option_toggle.dart new file mode 100644 index 00000000000..b405430d731 --- /dev/null +++ b/mobile/lib/widgets/backup/exp_upload_option_toggle.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; + +class ExpUploadOptionToggle extends HookConsumerWidget { + final VoidCallback? onToggle; + + const ExpUploadOptionToggle({ + super.key, + this.onToggle, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final storeService = ref.watch(storeServiceProvider); + final isNewUpload = useState(storeService.get(StoreKey.newUpload, false)); + + toggleNewUploadFeature() async { + final currentValue = storeService.get(StoreKey.newUpload, false); + await storeService.put(StoreKey.newUpload, !currentValue); + + onToggle?.call(); + } + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Container( + decoration: BoxDecoration( + color: context.colorScheme.surfaceContainerLow, + border: Border.all( + color: context.primaryColor, + width: 1.5, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), + child: ListTile( + title: Text( + "New Upload Experience", + style: context.textTheme.titleMedium?.copyWith( + color: context.colorScheme.primary, + ), + ), + subtitle: Text( + "Try the new upload experience with faster backups and improved reliability", + style: context.textTheme.labelLarge, + ), + trailing: Switch( + value: isNewUpload.value, + onChanged: (_) => toggleNewUploadFeature(), + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 09f81b9e1a1..a11f47c9b6c 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -4,10 +4,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -24,6 +26,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final storeService = ref.watch(storeServiceProvider); final BackUpState backupState = ref.watch(backupProvider); final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup; @@ -111,9 +114,13 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { buildBackupIndicator() { final indicatorIcon = getBackupBadgeIcon(); final badgeBackground = context.colorScheme.surfaceContainer; + final useExperimentalFeature = + storeService.get(StoreKey.newUpload, false); return InkWell( - onTap: () => context.pushRoute(const BackupControllerRoute()), + onTap: () => useExperimentalFeature + ? context.pushRoute(const ExpBackupRoute()) + : context.pushRoute(const BackupControllerRoute()), borderRadius: BorderRadius.circular(12), child: Badge( label: Container(