Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mobile): preserve mobile album info on upload #11965

Merged
merged 38 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dd5eb15
curating assets with albums to upload
alextran1502 Aug 21, 2024
5942732
sorting for background backup
alextran1502 Aug 21, 2024
00991f7
background upload works
alextran1502 Aug 21, 2024
c15682e
transform fields string array to javascript array
alextran1502 Aug 21, 2024
ee29bca
send json array
alextran1502 Aug 21, 2024
ec167cf
Merge branch 'main' of github.com:immich-app/immich into feat/mobile/…
alextran1502 Aug 21, 2024
9c13292
Merge branch 'feat/mobile/mirror-upload-album' of github.com:immich-a…
alextran1502 Aug 21, 2024
a1f1308
generate sql
alextran1502 Aug 22, 2024
b671bc1
refactor upload callback
alextran1502 Aug 22, 2024
2cdf0f7
remove albums info from upload payload
alextran1502 Aug 22, 2024
6943f55
mechanism to create album on album selection
alextran1502 Aug 22, 2024
c21929b
album creation
alextran1502 Aug 22, 2024
1e29873
Sync to upload album
alextran1502 Aug 22, 2024
180ce1a
Remove unused service
alextran1502 Aug 22, 2024
125488d
unify name changes
alextran1502 Aug 23, 2024
7272107
Add mechanism to sync uploaded assets to albums
alextran1502 Aug 23, 2024
92b4958
Put add to album operation after updating the UI state
alextran1502 Aug 23, 2024
54cdc66
clean up
alextran1502 Aug 23, 2024
42ed72e
background album sync
alextran1502 Aug 23, 2024
5532df1
add to album in background context
alextran1502 Aug 24, 2024
1ff0bbe
remove add to album in callback
alextran1502 Aug 24, 2024
507cac5
Merge branch 'main' of github.com:immich-app/immich into feat/mobile/…
alextran1502 Aug 24, 2024
70e9f5d
refactor
alextran1502 Aug 24, 2024
baf39ea
refactor
alextran1502 Aug 24, 2024
af42bfc
refactor
alextran1502 Aug 24, 2024
2472c4c
fix: make sure all selected albums are selected for building upload c…
alextran1502 Aug 24, 2024
24a97b8
clean up
alextran1502 Aug 25, 2024
9c901bd
add manual sync button
alextran1502 Aug 25, 2024
7da1f78
lint
alextran1502 Aug 25, 2024
b5a799d
revert server changes
alextran1502 Aug 25, 2024
7fba42b
Merge branch 'main' of github.com:immich-app/immich into feat/mobile/…
alextran1502 Aug 26, 2024
4b0d316
pr feedback
alextran1502 Aug 26, 2024
72b59e3
revert time filtering
alextran1502 Aug 26, 2024
8166c91
const
alextran1502 Aug 26, 2024
ce80459
sync album on manual upload
alextran1502 Aug 26, 2024
77ad569
linting
alextran1502 Aug 26, 2024
e722003
pr feedback and proper time filtering
alextran1502 Aug 26, 2024
45cdec8
wording
alextran1502 Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion mobile/assets/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -573,5 +573,9 @@
"version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"viewer_unstack": "Un-Stack"
"viewer_unstack": "Un-Stack",
"sync_albums": "Sync albums",
"sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich",
"sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums",
"sync": "Sync"
}
2 changes: 2 additions & 0 deletions mobile/lib/entities/store.entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ enum StoreKey<T> {
primaryColor<String>(128, type: String),
dynamicTheme<bool>(129, type: bool),
colorfulInterface<bool>(130, type: bool),

syncAlbums<bool>(131, type: bool),
;

const StoreKey(
Expand Down
19 changes: 19 additions & 0 deletions mobile/lib/models/backup/backup_candidate.model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:photo_manager/photo_manager.dart';

class BackupCandidate {
BackupCandidate({required this.asset, required this.albumNames});

AssetEntity asset;
List<String> albumNames;

@override
int get hashCode => asset.hashCode;

@override
bool operator ==(Object other) {
if (other is! BackupCandidate) {
return false;
}
return asset == other.asset;
}
}
6 changes: 3 additions & 3 deletions mobile/lib/models/backup/backup_state.model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import 'package:cancellation_token_http/http.dart';
import 'package:collection/collection.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';

import 'package:immich_mobile/models/backup/available_album.model.dart';
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
Expand Down Expand Up @@ -41,7 +41,7 @@ class BackUpState {
final Set<AvailableAlbum> excludedBackupAlbums;

/// Assets that are not overlapping in selected backup albums and excluded backup albums
final Set<AssetEntity> allUniqueAssets;
final Set<BackupCandidate> allUniqueAssets;

/// All assets from the selected albums that have been backup
final Set<String> selectedAlbumsBackupAssetsIds;
Expand Down Expand Up @@ -94,7 +94,7 @@ class BackUpState {
List<AvailableAlbum>? availableAlbums,
Set<AvailableAlbum>? selectedBackupAlbums,
Set<AvailableAlbum>? excludedBackupAlbums,
Set<AssetEntity>? allUniqueAssets,
Set<BackupCandidate>? allUniqueAssets,
Set<String>? selectedAlbumsBackupAssetsIds,
CurrentUploadAsset? currentUploadAsset,
}) {
Expand Down
42 changes: 42 additions & 0 deletions mobile/lib/models/backup/success_upload_asset.model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';

class SuccessUploadAsset {
final BackupCandidate candidate;
final String remoteAssetId;
final bool isDuplicate;

SuccessUploadAsset({
required this.candidate,
required this.remoteAssetId,
required this.isDuplicate,
});

SuccessUploadAsset copyWith({
BackupCandidate? candidate,
String? remoteAssetId,
bool? isDuplicate,
}) {
return SuccessUploadAsset(
candidate: candidate ?? this.candidate,
remoteAssetId: remoteAssetId ?? this.remoteAssetId,
isDuplicate: isDuplicate ?? this.isDuplicate,
);
}

@override
String toString() =>
'SuccessUploadAsset(asset: $candidate, remoteAssetId: $remoteAssetId, isDuplicate: $isDuplicate)';

@override
bool operator ==(covariant SuccessUploadAsset other) {
if (identical(this, other)) return true;

return other.candidate == candidate &&
other.remoteAssetId == remoteAssetId &&
other.isDuplicate == isDuplicate;
}

@override
int get hashCode =>
candidate.hashCode ^ remoteAssetId.hashCode ^ isDuplicate.hashCode;
}
70 changes: 28 additions & 42 deletions mobile/lib/pages/backup/backup_album_selection.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/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';
import 'package:immich_mobile/widgets/backup/album_info_list_tile.dart';
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';

@RoutePage()
class BackupAlbumSelectionPage extends HookConsumerWidget {
const BackupAlbumSelectionPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// final availableAlbums = ref.watch(backupProvider).availableAlbums;
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;

Expand Down Expand Up @@ -144,47 +149,14 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
}).toSet();
}

// buildSearchBar() {
// return Padding(
// padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 8.0),
// child: TextFormField(
// onChanged: (searchValue) {
// // if (searchValue.isEmpty) {
// // albums = availableAlbums;
// // } else {
// // albums.value = availableAlbums
// // .where(
// // (album) => album.name
// // .toLowerCase()
// // .contains(searchValue.toLowerCase()),
// // )
// // .toList();
// // }
// },
// decoration: InputDecoration(
// contentPadding: const EdgeInsets.symmetric(
// horizontal: 8.0,
// vertical: 8.0,
// ),
// hintText: "Search",
// hintStyle: TextStyle(
// color: isDarkTheme ? Colors.white : Colors.grey,
// fontSize: 14.0,
// ),
// prefixIcon: const Icon(
// Icons.search,
// color: Colors.grey,
// ),
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide.none,
// ),
// filled: true,
// fillColor: isDarkTheme ? Colors.white30 : Colors.grey[200],
// ),
// ),
// );
// }
handleSyncAlbumToggle(bool isEnable) async {
if (isEnable) {
await ref.read(albumProvider.notifier).getAllAlbums();
for (final album in selectedBackupAlbums) {
await ref.read(albumProvider.notifier).createSyncAlbum(album.name);
}
}
}

return Scaffold(
appBar: AppBar(
Expand Down Expand Up @@ -226,6 +198,20 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
),
),

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(
Expand Down
18 changes: 18 additions & 0 deletions mobile/lib/providers/album/album.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AlbumNotifier extends StateNotifier<List<Album>> {
});
_streamSub = query.watch().listen((data) => state = data);
}

final AlbumService _albumService;
late final StreamSubscription<List<Album>> _streamSub;

Expand All @@ -41,6 +42,23 @@ class AlbumNotifier extends StateNotifier<List<Album>> {
) =>
_albumService.createAlbum(albumTitle, assets, []);

Future<Album?> getAlbumByName(String albumName, {bool remoteOnly = false}) =>
_albumService.getAlbumByName(albumName, remoteOnly);

/// Create an album on the server with the same name as the selected album for backup
/// First this will check if the album already exists on the server with name
/// If it does not exist, it will create the album on the server
Future<void> createSyncAlbum(
String albumName,
) async {
final album = await getAlbumByName(albumName, remoteOnly: true);
if (album != null) {
return;
}

await createAlbum(albumName, {});
}

@override
void dispose() {
_streamSub.cancel();
Expand Down
Loading
Loading