mirror of
https://github.com/immich-app/immich
synced 2025-06-10 21:59:17 +00:00
wip
This commit is contained in:
parent
2b6672293b
commit
b19f9c40ef
@ -68,7 +68,8 @@ enum StoreKey<T> {
|
|||||||
manageLocalMediaAndroid<bool>._(137),
|
manageLocalMediaAndroid<bool>._(137),
|
||||||
|
|
||||||
// Experimental stuff
|
// Experimental stuff
|
||||||
photoManagerCustomFilter<bool>._(1000);
|
photoManagerCustomFilter<bool>._(1000),
|
||||||
|
newUpload<bool>._(1001);
|
||||||
|
|
||||||
const StoreKey._(this.id);
|
const StoreKey._(this.id);
|
||||||
final int id;
|
final int id;
|
||||||
|
@ -3,11 +3,9 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.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.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/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/album_info_card.dart';
|
import 'package:immich_mobile/widgets/backup/album_info_card.dart';
|
||||||
@ -19,17 +17,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
const BackupAlbumSelectionPage({super.key});
|
const BackupAlbumSelectionPage({super.key});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final albums = ref.watch(backupAlbumProvider);
|
final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||||
|
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
|
||||||
final selectedBackupAlbums = albums
|
|
||||||
.where((album) => album.backupSelection == BackupSelection.selected)
|
|
||||||
.toList();
|
|
||||||
final excludedBackupAlbums = albums
|
|
||||||
.where((album) => album.backupSelection == BackupSelection.excluded)
|
|
||||||
.toList();
|
|
||||||
final enableSyncUploadAlbum =
|
final enableSyncUploadAlbum =
|
||||||
useAppSettingsState(AppSettingsEnum.syncAlbums);
|
useAppSettingsState(AppSettingsEnum.syncAlbums);
|
||||||
final isDarkTheme = context.isDarkTheme;
|
final isDarkTheme = context.isDarkTheme;
|
||||||
|
final albums = ref.watch(backupProvider).availableAlbums;
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
@ -92,9 +85,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
buildSelectedAlbumNameChip() {
|
buildSelectedAlbumNameChip() {
|
||||||
return selectedBackupAlbums.map((album) {
|
return selectedBackupAlbums.map((album) {
|
||||||
void removeSelection() {
|
void removeSelection() =>
|
||||||
ref.read(backupAlbumProvider.notifier).deselectAlbum(album);
|
ref.read(backupProvider.notifier).removeAlbumForBackup(album);
|
||||||
}
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
@ -125,7 +117,9 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
buildExcludedAlbumNameChip() {
|
buildExcludedAlbumNameChip() {
|
||||||
return excludedBackupAlbums.map((album) {
|
return excludedBackupAlbums.map((album) {
|
||||||
void removeSelection() {
|
void removeSelection() {
|
||||||
ref.read(backupAlbumProvider.notifier).deselectAlbum(album);
|
ref
|
||||||
|
.watch(backupProvider.notifier)
|
||||||
|
.removeExcludedAlbumForBackup(album);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@ -174,131 +168,129 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
).tr(),
|
).tr(),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: CustomScrollView(
|
||||||
child: CustomScrollView(
|
physics: const ClampingScrollPhysics(),
|
||||||
physics: const ClampingScrollPhysics(),
|
slivers: [
|
||||||
slivers: [
|
SliverToBoxAdapter(
|
||||||
SliverToBoxAdapter(
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
vertical: 8.0,
|
||||||
vertical: 8.0,
|
horizontal: 16.0,
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
"backup_album_selection_page_selection_info",
|
|
||||||
style: context.textTheme.titleSmall,
|
|
||||||
).tr(),
|
|
||||||
),
|
),
|
||||||
// Selected Album Chips
|
child: Text(
|
||||||
|
"backup_album_selection_page_selection_info",
|
||||||
|
style: context.textTheme.titleSmall,
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
// Selected Album Chips
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: [
|
children: [
|
||||||
...buildSelectedAlbumNameChip(),
|
...buildSelectedAlbumNameChip(),
|
||||||
...buildExcludedAlbumNameChip(),
|
...buildExcludedAlbumNameChip(),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: enableSyncUploadAlbum,
|
valueNotifier: enableSyncUploadAlbum,
|
||||||
title: "sync_albums".tr(),
|
title: "sync_albums".tr(),
|
||||||
subtitle: "sync_upload_album_setting_subtitle".tr(),
|
subtitle: "sync_upload_album_setting_subtitle".tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
titleStyle: context.textTheme.bodyLarge?.copyWith(
|
titleStyle: context.textTheme.bodyLarge?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
|
||||||
subtitleStyle: context.textTheme.labelLarge?.copyWith(
|
|
||||||
color: context.colorScheme.primary,
|
|
||||||
),
|
|
||||||
onChanged: handleSyncAlbumToggle,
|
|
||||||
),
|
),
|
||||||
|
subtitleStyle: context.textTheme.labelLarge?.copyWith(
|
||||||
|
color: context.colorScheme.primary,
|
||||||
|
),
|
||||||
|
onChanged: handleSyncAlbumToggle,
|
||||||
|
),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
"backup_album_selection_page_albums_device".tr(
|
"backup_album_selection_page_albums_device".tr(
|
||||||
namedArgs: {
|
namedArgs: {
|
||||||
'count': ref
|
'count': ref
|
||||||
.watch(backupProvider)
|
.watch(backupProvider)
|
||||||
.availableAlbums
|
.availableAlbums
|
||||||
.length
|
.length
|
||||||
.toString(),
|
.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(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
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) {
|
SliverLayoutBuilder(
|
||||||
if (constraints.crossAxisExtent > 600) {
|
builder: (context, constraints) {
|
||||||
return buildAlbumSelectionGrid();
|
if (constraints.crossAxisExtent > 600) {
|
||||||
} else {
|
return buildAlbumSelectionGrid();
|
||||||
return buildAlbumSelectionList();
|
} else {
|
||||||
}
|
return buildAlbumSelectionList();
|
||||||
},
|
}
|
||||||
),
|
},
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,21 +6,19 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.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/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.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/error_backup_list.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/ios_background_settings.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/backup/manual_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.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/backup_info_card.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.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';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -88,13 +86,8 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Widget buildSelectedAlbumName() {
|
Widget buildSelectedAlbumName() {
|
||||||
String text = "backup_controller_page_backup_selected".tr();
|
var text = "backup_controller_page_backup_selected".tr();
|
||||||
final albums = ref
|
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||||
.watch(backupAlbumProvider)
|
|
||||||
.where(
|
|
||||||
(album) => album.backupSelection == BackupSelection.selected,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (albums.isNotEmpty) {
|
if (albums.isNotEmpty) {
|
||||||
for (var album in albums) {
|
for (var album in albums) {
|
||||||
@ -128,13 +121,8 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildExcludedAlbumName() {
|
Widget buildExcludedAlbumName() {
|
||||||
String text = "backup_controller_page_excluded".tr();
|
var text = "backup_controller_page_excluded".tr();
|
||||||
final albums = ref
|
var albums = ref.watch(backupProvider).excludedBackupAlbums;
|
||||||
.watch(backupAlbumProvider)
|
|
||||||
.where(
|
|
||||||
(album) => album.backupSelection == BackupSelection.excluded,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (albums.isNotEmpty) {
|
if (albums.isNotEmpty) {
|
||||||
for (var album in albums) {
|
for (var album in albums) {
|
||||||
@ -315,11 +303,19 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
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(
|
child: ListView(
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: hasAnyAlbum
|
children: hasAnyAlbum
|
||||||
? [
|
? [
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ExpUploadOptionToggle(
|
||||||
|
onToggle: () =>
|
||||||
|
context.replaceRoute(const ExpBackupRoute()),
|
||||||
|
),
|
||||||
buildFolderSelectionTile(),
|
buildFolderSelectionTile(),
|
||||||
BackupInfoCard(
|
BackupInfoCard(
|
||||||
title: "total".tr(),
|
title: "total".tr(),
|
||||||
@ -346,32 +342,6 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
const CurrentUploadingAssetInfoBox(),
|
const CurrentUploadingAssetInfoBox(),
|
||||||
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
||||||
buildBackupButton(),
|
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(),
|
buildFolderSelectionTile(),
|
||||||
|
304
mobile/lib/pages/backup/exp_backup_album_selection.page.dart
Normal file
304
mobile/lib/pages/backup/exp_backup_album_selection.page.dart
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
395
mobile/lib/pages/backup/exp_backup_controller.page.dart
Normal file
395
mobile/lib/pages/backup/exp_backup_controller.page.dart
Normal file
@ -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(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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/album/album_viewer.page.dart';
|
||||||
import 'package:immich_mobile/pages/albums/albums.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/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_album_selection.page.dart';
|
||||||
import 'package:immich_mobile/pages/backup/backup_controller.page.dart';
|
import 'package:immich_mobile/pages/backup/backup_controller.page.dart';
|
||||||
import 'package:immich_mobile/pages/backup/backup_options.page.dart';
|
import 'package:immich_mobile/pages/backup/backup_options.page.dart';
|
||||||
@ -330,5 +332,9 @@ class AppRouter extends RootStackRouter {
|
|||||||
page: RemoteMediaSummaryRoute.page,
|
page: RemoteMediaSummaryRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
),
|
),
|
||||||
|
AutoRoute(
|
||||||
|
page: ExpBackupRoute.page,
|
||||||
|
guards: [_authGuard, _duplicateGuard],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -608,6 +608,38 @@ class EditImageRouteArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [ExpBackupAlbumSelectionPage]
|
||||||
|
class ExpBackupAlbumSelectionRoute extends PageRouteInfo<void> {
|
||||||
|
const ExpBackupAlbumSelectionRoute({List<PageRouteInfo>? 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<void> {
|
||||||
|
const ExpBackupRoute({List<PageRouteInfo>? children})
|
||||||
|
: super(ExpBackupRoute.name, initialChildren: children);
|
||||||
|
|
||||||
|
static const String name = 'ExpBackupRoute';
|
||||||
|
|
||||||
|
static PageInfo page = PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
return const ExpBackupPage();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [FailedBackupStatusPage]
|
/// [FailedBackupStatusPage]
|
||||||
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
||||||
|
@ -3,19 +3,18 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/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/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.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.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
|
||||||
class AlbumInfoCard extends HookConsumerWidget {
|
class AlbumInfoCard extends HookConsumerWidget {
|
||||||
final LocalAlbum album;
|
final AvailableAlbum album;
|
||||||
|
|
||||||
const AlbumInfoCard({
|
const AlbumInfoCard({
|
||||||
super.key,
|
super.key,
|
||||||
@ -24,8 +23,10 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final bool isSelected = album.backupSelection == BackupSelection.selected;
|
final bool isSelected =
|
||||||
final bool isExcluded = album.backupSelection == BackupSelection.excluded;
|
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||||
|
final bool isExcluded =
|
||||||
|
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||||
final syncAlbum = ref
|
final syncAlbum = ref
|
||||||
.watch(appSettingsServiceProvider)
|
.watch(appSettingsServiceProvider)
|
||||||
.getSetting(AppSettingsEnum.syncAlbums);
|
.getSetting(AppSettingsEnum.syncAlbums);
|
||||||
@ -90,10 +91,9 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
ref.read(backupAlbumProvider.notifier).deselectAlbum(album);
|
ref.read(backupProvider.notifier).removeAlbumForBackup(album);
|
||||||
} else {
|
} else {
|
||||||
ref.read(backupAlbumProvider.notifier).selectAlbum(album);
|
ref.read(backupProvider.notifier).addAlbumForBackup(album);
|
||||||
|
|
||||||
if (syncAlbum) {
|
if (syncAlbum) {
|
||||||
ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
||||||
}
|
}
|
||||||
@ -103,8 +103,11 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
||||||
|
|
||||||
if (isExcluded) {
|
if (isExcluded) {
|
||||||
ref.read(backupAlbumProvider.notifier).deselectAlbum(album);
|
// Remove from exclude album list
|
||||||
|
ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album);
|
||||||
} else {
|
} else {
|
||||||
|
// Add to exclude album list
|
||||||
|
|
||||||
if (album.id == 'isAll' || album.name == 'Recents') {
|
if (album.id == 'isAll' || album.name == 'Recents') {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
@ -114,7 +117,8 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ref.read(backupAlbumProvider.notifier).excludeAlbum(album);
|
|
||||||
|
ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
@ -177,16 +181,25 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
fontWeight: FontWeight.bold,
|
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(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: refactor below
|
context.pushRoute(
|
||||||
|
AlbumPreviewRoute(album: album.album),
|
||||||
// context.pushRoute(
|
);
|
||||||
// AlbumPreviewRoute(album: album.album),
|
|
||||||
// );
|
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.image_outlined,
|
Icons.image_outlined,
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/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/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.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/providers/haptic_feedback.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
|
||||||
class AlbumInfoListTile extends HookConsumerWidget {
|
class AlbumInfoListTile extends HookConsumerWidget {
|
||||||
final LocalAlbum album;
|
final AvailableAlbum album;
|
||||||
|
|
||||||
const AlbumInfoListTile({super.key, required this.album});
|
const AlbumInfoListTile({super.key, required this.album});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final bool isSelected = album.backupSelection == BackupSelection.selected;
|
final bool isSelected =
|
||||||
final bool isExcluded = album.backupSelection == BackupSelection.excluded;
|
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||||
|
final bool isExcluded =
|
||||||
|
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||||
final syncAlbum = ref
|
final syncAlbum = ref
|
||||||
.watch(appSettingsServiceProvider)
|
.watch(appSettingsServiceProvider)
|
||||||
.getSetting(AppSettingsEnum.syncAlbums);
|
.getSetting(AppSettingsEnum.syncAlbums);
|
||||||
@ -64,8 +67,11 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
ref.watch(hapticFeedbackProvider.notifier).selectionClick();
|
ref.watch(hapticFeedbackProvider.notifier).selectionClick();
|
||||||
|
|
||||||
if (isExcluded) {
|
if (isExcluded) {
|
||||||
ref.read(backupAlbumProvider.notifier).deselectAlbum(album);
|
// Remove from exclude album list
|
||||||
|
ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album);
|
||||||
} else {
|
} else {
|
||||||
|
// Add to exclude album list
|
||||||
|
|
||||||
if (album.id == 'isAll' || album.name == 'Recents') {
|
if (album.id == 'isAll' || album.name == 'Recents') {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
@ -76,7 +82,7 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.read(backupAlbumProvider.notifier).excludeAlbum(album);
|
ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
@ -85,9 +91,9 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
ref.read(backupAlbumProvider.notifier).deselectAlbum(album);
|
ref.read(backupProvider.notifier).removeAlbumForBackup(album);
|
||||||
} else {
|
} else {
|
||||||
ref.read(backupAlbumProvider.notifier).selectAlbum(album);
|
ref.read(backupProvider.notifier).addAlbumForBackup(album);
|
||||||
if (syncAlbum) {
|
if (syncAlbum) {
|
||||||
ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
||||||
}
|
}
|
||||||
@ -104,10 +110,9 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
subtitle: Text(album.assetCount.toString()),
|
subtitle: Text(album.assetCount.toString()),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// TODO: refactor below
|
context.pushRoute(
|
||||||
// context.pushRoute(
|
AlbumPreviewRoute(album: album.album),
|
||||||
// AlbumPreviewRoute(album: album.album),
|
);
|
||||||
// );
|
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.image_outlined,
|
Icons.image_outlined,
|
||||||
|
122
mobile/lib/widgets/backup/exp_album_info_list_tile.dart
Normal file
122
mobile/lib/widgets/backup/exp_album_info_list_tile.dart
Normal file
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
61
mobile/lib/widgets/backup/exp_upload_option_toggle.dart
Normal file
61
mobile/lib/widgets/backup/exp_upload_option_toggle.dart
Normal file
@ -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(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,12 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.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/models/server_info/server_info.model.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.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/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
@ -24,6 +26,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final storeService = ref.watch(storeServiceProvider);
|
||||||
final BackUpState backupState = ref.watch(backupProvider);
|
final BackUpState backupState = ref.watch(backupProvider);
|
||||||
final bool isEnableAutoBackup =
|
final bool isEnableAutoBackup =
|
||||||
backupState.backgroundBackup || backupState.autoBackup;
|
backupState.backgroundBackup || backupState.autoBackup;
|
||||||
@ -111,9 +114,13 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
buildBackupIndicator() {
|
buildBackupIndicator() {
|
||||||
final indicatorIcon = getBackupBadgeIcon();
|
final indicatorIcon = getBackupBadgeIcon();
|
||||||
final badgeBackground = context.colorScheme.surfaceContainer;
|
final badgeBackground = context.colorScheme.surfaceContainer;
|
||||||
|
final useExperimentalFeature =
|
||||||
|
storeService.get(StoreKey.newUpload, false);
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => context.pushRoute(const BackupControllerRoute()),
|
onTap: () => useExperimentalFeature
|
||||||
|
? context.pushRoute(const ExpBackupRoute())
|
||||||
|
: context.pushRoute(const BackupControllerRoute()),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Badge(
|
child: Badge(
|
||||||
label: Container(
|
label: Container(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user