mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:39:03 -04:00 
			
		
		
		
	feat(mobile): render assets on device by default (#10470)
* feat(mobile): render asset on device by default * remove unused service
This commit is contained in:
		
							parent
							
								
									6164640575
								
							
						
					
					
						commit
						32da9d90e4
					
				| @ -1,13 +1,8 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:collection'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart'; | ||||
| import 'package:immich_mobile/entities/backup_album.entity.dart'; | ||||
| import 'package:immich_mobile/services/backup.service.dart'; | ||||
| import 'package:immich_mobile/entities/album.entity.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/entities/store.entity.dart'; | ||||
| @ -28,7 +23,6 @@ final albumServiceProvider = Provider( | ||||
|     ref.watch(userServiceProvider), | ||||
|     ref.watch(syncServiceProvider), | ||||
|     ref.watch(dbProvider), | ||||
|     ref.watch(backupServiceProvider), | ||||
|   ), | ||||
| ); | ||||
| 
 | ||||
| @ -37,7 +31,6 @@ class AlbumService { | ||||
|   final UserService _userService; | ||||
|   final SyncService _syncService; | ||||
|   final Isar _db; | ||||
|   final BackupService _backupService; | ||||
|   final Logger _log = Logger('AlbumService'); | ||||
|   Completer<bool> _localCompleter = Completer()..complete(false); | ||||
|   Completer<bool> _remoteCompleter = Completer()..complete(false); | ||||
| @ -47,7 +40,6 @@ class AlbumService { | ||||
|     this._userService, | ||||
|     this._syncService, | ||||
|     this._db, | ||||
|     this._backupService, | ||||
|   ); | ||||
| 
 | ||||
|   /// Checks all selected device albums for changes of albums and their assets | ||||
| @ -62,60 +54,14 @@ class AlbumService { | ||||
|     final Stopwatch sw = Stopwatch()..start(); | ||||
|     bool changes = false; | ||||
|     try { | ||||
|       final List<String> excludedIds = | ||||
|           await _backupService.excludedAlbumsQuery().idProperty().findAll(); | ||||
|       final List<String> selectedIds = | ||||
|           await _backupService.selectedAlbumsQuery().idProperty().findAll(); | ||||
|       if (selectedIds.isEmpty) { | ||||
|         final numLocal = await _db.albums.where().localIdIsNotNull().count(); | ||||
|         if (numLocal > 0) { | ||||
|           _syncService.removeAllLocalAlbumsAndAssets(); | ||||
|         } | ||||
|         return false; | ||||
|       } | ||||
|       final List<AssetPathEntity> onDevice = | ||||
|           await PhotoManager.getAssetPathList( | ||||
|         hasAll: true, | ||||
|         filterOption: FilterOptionGroup(containsPathModified: true), | ||||
|       ); | ||||
|       _log.info("Found ${onDevice.length} device albums"); | ||||
|       Set<String>? excludedAssets; | ||||
|       if (excludedIds.isNotEmpty) { | ||||
|         if (Platform.isIOS) { | ||||
|           // iOS and Android device album working principle differ significantly | ||||
|           // on iOS, an asset can be in multiple albums | ||||
|           // on Android, an asset can only be in exactly one album (folder!) at the same time | ||||
|           // thus, on Android, excluding an album can be done by ignoring that album | ||||
|           // however, on iOS, it it necessary to load the assets from all excluded | ||||
|           // albums and check every asset from any selected album against the set | ||||
|           // of excluded assets | ||||
|           excludedAssets = await _loadExcludedAssetIds(onDevice, excludedIds); | ||||
|           _log.info("Found ${excludedAssets.length} assets to exclude"); | ||||
|         } | ||||
|         // remove all excluded albums | ||||
|         onDevice.removeWhere((e) => excludedIds.contains(e.id)); | ||||
|         _log.info( | ||||
|           "Ignoring ${excludedIds.length} excluded albums resulting in ${onDevice.length} device albums", | ||||
|         ); | ||||
|       } | ||||
|       final hasAll = selectedIds | ||||
|           .map((id) => onDevice.firstWhereOrNull((a) => a.id == id)) | ||||
|           .whereNotNull() | ||||
|           .any((a) => a.isAll); | ||||
|       if (hasAll) { | ||||
|         if (Platform.isAndroid) { | ||||
|           // remove the virtual "Recent" album and keep and individual albums | ||||
|           // on Android, the virtual "Recent" `lastModified` value is always null | ||||
|           onDevice.removeWhere((e) => e.isAll); | ||||
|           _log.info("'Recents' is selected, keeping all individual albums"); | ||||
|         } | ||||
|       } else { | ||||
|         // keep only the explicitly selected albums | ||||
|         onDevice.removeWhere((e) => !selectedIds.contains(e.id)); | ||||
|         _log.info("'Recents' is not selected, keeping only selected albums"); | ||||
|       } | ||||
|       changes = | ||||
|           await _syncService.syncLocalAlbumAssetsToDb(onDevice, excludedAssets); | ||||
| 
 | ||||
|       changes = await _syncService.syncLocalAlbumAssetsToDb(onDevice); | ||||
|       _log.info("Syncing completed. Changes: $changes"); | ||||
|     } finally { | ||||
|       _localCompleter.complete(changes); | ||||
| @ -124,21 +70,6 @@ class AlbumService { | ||||
|     return changes; | ||||
|   } | ||||
| 
 | ||||
|   Future<Set<String>> _loadExcludedAssetIds( | ||||
|     List<AssetPathEntity> albums, | ||||
|     List<String> excludedAlbumIds, | ||||
|   ) async { | ||||
|     final Set<String> result = HashSet<String>(); | ||||
|     for (AssetPathEntity a in albums) { | ||||
|       if (excludedAlbumIds.contains(a.id)) { | ||||
|         final List<AssetEntity> assets = | ||||
|             await a.getAssetListRange(start: 0, end: 0x7fffffffffffffff); | ||||
|         result.addAll(assets.map((e) => e.id)); | ||||
|       } | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   /// Checks remote albums (owned if `isShared` is false) for changes, | ||||
|   /// updates the local database and returns `true` if there were any changes | ||||
|   Future<bool> refreshRemoteAlbums({required bool isShared}) async { | ||||
|  | ||||
| @ -24,13 +24,9 @@ class HashService { | ||||
|     AssetPathEntity album, { | ||||
|     int start = 0, | ||||
|     int end = 0x7fffffffffffffff, | ||||
|     Set<String>? excludedAssets, | ||||
|   }) async { | ||||
|     final entities = await album.getAssetListRange(start: start, end: end); | ||||
|     final filtered = excludedAssets == null | ||||
|         ? entities | ||||
|         : entities.where((e) => !excludedAssets.contains(e.id)).toList(); | ||||
|     return _hashAssets(filtered); | ||||
|     return _hashAssets(entities); | ||||
|   } | ||||
| 
 | ||||
|   /// Converts a list of [AssetEntity]s to [Asset]s including only those | ||||
|  | ||||
| @ -68,10 +68,9 @@ class SyncService { | ||||
|   /// Syncs all device albums and their assets to the database | ||||
|   /// Returns `true` if there were any changes | ||||
|   Future<bool> syncLocalAlbumAssetsToDb( | ||||
|     List<AssetPathEntity> onDevice, [ | ||||
|     Set<String>? excludedAssets, | ||||
|   ]) => | ||||
|       _lock.run(() => _syncLocalAlbumAssetsToDb(onDevice, excludedAssets)); | ||||
|     List<AssetPathEntity> onDevice, | ||||
|   ) => | ||||
|       _lock.run(() => _syncLocalAlbumAssetsToDb(onDevice)); | ||||
| 
 | ||||
|   /// returns all Asset IDs that are not contained in the existing list | ||||
|   List<int> sharedAssetsToRemove( | ||||
| @ -492,9 +491,8 @@ class SyncService { | ||||
|   /// Syncs all device albums and their assets to the database | ||||
|   /// Returns `true` if there were any changes | ||||
|   Future<bool> _syncLocalAlbumAssetsToDb( | ||||
|     List<AssetPathEntity> onDevice, [ | ||||
|     Set<String>? excludedAssets, | ||||
|   ]) async { | ||||
|     List<AssetPathEntity> onDevice, | ||||
|   ) async { | ||||
|     onDevice.sort((a, b) => a.id.compareTo(b.id)); | ||||
|     final inDb = | ||||
|         await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll(); | ||||
| @ -510,10 +508,8 @@ class SyncService { | ||||
|         album, | ||||
|         deleteCandidates, | ||||
|         existing, | ||||
|         excludedAssets, | ||||
|       ), | ||||
|       onlyFirst: (AssetPathEntity ape) => | ||||
|           _addAlbumFromDevice(ape, existing, excludedAssets), | ||||
|       onlyFirst: (AssetPathEntity ape) => _addAlbumFromDevice(ape, existing), | ||||
|       onlySecond: (Album a) => _removeAlbumFromDb(a, deleteCandidates), | ||||
|     ); | ||||
|     _log.fine( | ||||
| @ -545,16 +541,13 @@ class SyncService { | ||||
|     Album album, | ||||
|     List<Asset> deleteCandidates, | ||||
|     List<Asset> existing, [ | ||||
|     Set<String>? excludedAssets, | ||||
|     bool forceRefresh = false, | ||||
|   ]) async { | ||||
|     if (!forceRefresh && !await _hasAssetPathEntityChanged(ape, album)) { | ||||
|       _log.fine("Local album ${ape.name} has not changed. Skipping sync."); | ||||
|       return false; | ||||
|     } | ||||
|     if (!forceRefresh && | ||||
|         excludedAssets == null && | ||||
|         await _syncDeviceAlbumFast(ape, album)) { | ||||
|     if (!forceRefresh && await _syncDeviceAlbumFast(ape, album)) { | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
| @ -566,8 +559,7 @@ class SyncService { | ||||
|         .findAll(); | ||||
|     assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!"); | ||||
|     final int assetCountOnDevice = await ape.assetCountAsync; | ||||
|     final List<Asset> onDevice = | ||||
|         await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets); | ||||
|     final List<Asset> onDevice = await _hashService.getHashedAssets(ape); | ||||
|     _removeDuplicates(onDevice); | ||||
|     // _removeDuplicates sorts `onDevice` by checksum | ||||
|     final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb); | ||||
| @ -678,13 +670,11 @@ class SyncService { | ||||
|   /// assets already existing in the database to the list of `existing` assets | ||||
|   Future<void> _addAlbumFromDevice( | ||||
|     AssetPathEntity ape, | ||||
|     List<Asset> existing, [ | ||||
|     Set<String>? excludedAssets, | ||||
|   ]) async { | ||||
|     List<Asset> existing, | ||||
|   ) async { | ||||
|     _log.info("Syncing a new local album to DB: ${ape.name}"); | ||||
|     final Album a = Album.local(ape); | ||||
|     final assets = | ||||
|         await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets); | ||||
|     final assets = await _hashService.getHashedAssets(ape); | ||||
|     _removeDuplicates(assets); | ||||
|     final (existingInDb, updated) = await _linkWithExistingFromDb(assets); | ||||
|     _log.info( | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user