mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	refactor(mobile): introduce Album & User classes (#1561)
replace usages of AlbumResponseDto with Album replace usages of UserResponseDto with User
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							527aa61a87
						
					
				
				
					commit
					2139853dd9
				
			| @@ -2,24 +2,26 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/services/album.service.dart'; | ||||
| import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
|  | ||||
| class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
| class AlbumNotifier extends StateNotifier<List<Album>> { | ||||
|   AlbumNotifier(this._albumService, this._albumCacheService) : super([]); | ||||
|   final AlbumService _albumService; | ||||
|   final AlbumCacheService _albumCacheService; | ||||
|  | ||||
|   _cacheState() { | ||||
|   void _cacheState() { | ||||
|     _albumCacheService.put(state); | ||||
|   } | ||||
|  | ||||
|   getAllAlbums() async { | ||||
|   Future<void> getAllAlbums() async { | ||||
|     if (await _albumCacheService.isValid() && state.isEmpty) { | ||||
|       state = await _albumCacheService.get(); | ||||
|       final albums = await _albumCacheService.get(); | ||||
|       if (albums != null) { | ||||
|         state = albums; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     List<AlbumResponseDto>? albums = | ||||
|         await _albumService.getAlbums(isShared: false); | ||||
|     final albums = await _albumService.getAlbums(isShared: false); | ||||
|  | ||||
|     if (albums != null) { | ||||
|       state = albums; | ||||
| @@ -27,17 +29,16 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   deleteAlbum(String albumId) { | ||||
|     state = state.where((album) => album.id != albumId).toList(); | ||||
|   void deleteAlbum(Album album) { | ||||
|     state = state.where((a) => a.id != album.id).toList(); | ||||
|     _cacheState(); | ||||
|   } | ||||
|  | ||||
|   Future<AlbumResponseDto?> createAlbum( | ||||
|   Future<Album?> createAlbum( | ||||
|     String albumTitle, | ||||
|     Set<Asset> assets, | ||||
|   ) async { | ||||
|     AlbumResponseDto? album = | ||||
|         await _albumService.createAlbum(albumTitle, assets, []); | ||||
|     Album? album = await _albumService.createAlbum(albumTitle, assets, []); | ||||
|  | ||||
|     if (album != null) { | ||||
|       state = [...state, album]; | ||||
| @@ -49,8 +50,7 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
|   } | ||||
| } | ||||
|  | ||||
| final albumProvider = | ||||
|     StateNotifierProvider<AlbumNotifier, List<AlbumResponseDto>>((ref) { | ||||
| final albumProvider = StateNotifierProvider<AlbumNotifier, List<Album>>((ref) { | ||||
|   return AlbumNotifier( | ||||
|     ref.watch(albumServiceProvider), | ||||
|     ref.watch(albumCacheServiceProvider), | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/models/album_viewer_page_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; | ||||
| import 'package:immich_mobile/modules/album/services/album.service.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
|  | ||||
| class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> { | ||||
|   AlbumViewerNotifier(this.ref) | ||||
| @@ -30,14 +31,12 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> { | ||||
|   } | ||||
|  | ||||
|   Future<bool> changeAlbumTitle( | ||||
|     String albumId, | ||||
|     String ownerId, | ||||
|     Album album, | ||||
|     String newAlbumTitle, | ||||
|   ) async { | ||||
|     AlbumService service = ref.watch(albumServiceProvider); | ||||
|  | ||||
|     bool isSuccess = | ||||
|         await service.changeTitleAlbum(albumId, ownerId, newAlbumTitle); | ||||
|     bool isSuccess = await service.changeTitleAlbum(album, newAlbumTitle); | ||||
|  | ||||
|     if (isSuccess) { | ||||
|       state = state.copyWith(editTitleText: "", isEditAlbum: false); | ||||
|   | ||||
| @@ -2,30 +2,31 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/services/album.service.dart'; | ||||
| import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
|  | ||||
| class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
| class SharedAlbumNotifier extends StateNotifier<List<Album>> { | ||||
|   SharedAlbumNotifier(this._albumService, this._sharedAlbumCacheService) | ||||
|       : super([]); | ||||
|  | ||||
|   final AlbumService _albumService; | ||||
|   final SharedAlbumCacheService _sharedAlbumCacheService; | ||||
|  | ||||
|   _cacheState() { | ||||
|   void _cacheState() { | ||||
|     _sharedAlbumCacheService.put(state); | ||||
|   } | ||||
|  | ||||
|   Future<AlbumResponseDto?> createSharedAlbum( | ||||
|   Future<Album?> createSharedAlbum( | ||||
|     String albumName, | ||||
|     Set<Asset> assets, | ||||
|     List<String> sharedUserIds, | ||||
|     Iterable<Asset> assets, | ||||
|     Iterable<User> sharedUsers, | ||||
|   ) async { | ||||
|     try { | ||||
|       var newAlbum = await _albumService.createAlbum( | ||||
|         albumName, | ||||
|         assets, | ||||
|         sharedUserIds, | ||||
|         sharedUsers, | ||||
|       ); | ||||
|  | ||||
|       if (newAlbum != null) { | ||||
| @@ -41,13 +42,15 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getAllSharedAlbums() async { | ||||
|   Future<void> getAllSharedAlbums() async { | ||||
|     if (await _sharedAlbumCacheService.isValid() && state.isEmpty) { | ||||
|       state = await _sharedAlbumCacheService.get(); | ||||
|       final albums = await _sharedAlbumCacheService.get(); | ||||
|       if (albums != null) { | ||||
|         state = albums; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     List<AlbumResponseDto>? sharedAlbums = | ||||
|         await _albumService.getAlbums(isShared: true); | ||||
|     List<Album>? sharedAlbums = await _albumService.getAlbums(isShared: true); | ||||
|  | ||||
|     if (sharedAlbums != null) { | ||||
|       state = sharedAlbums; | ||||
| @@ -55,16 +58,16 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   deleteAlbum(String albumId) async { | ||||
|     state = state.where((album) => album.id != albumId).toList(); | ||||
|   void deleteAlbum(Album album) { | ||||
|     state = state.where((a) => a.id != album.id).toList(); | ||||
|     _cacheState(); | ||||
|   } | ||||
|  | ||||
|   Future<bool> leaveAlbum(String albumId) async { | ||||
|     var res = await _albumService.leaveAlbum(albumId); | ||||
|   Future<bool> leaveAlbum(Album album) async { | ||||
|     var res = await _albumService.leaveAlbum(album); | ||||
|  | ||||
|     if (res) { | ||||
|       state = state.where((album) => album.id != albumId).toList(); | ||||
|       state = state.where((a) => a.id != album.id).toList(); | ||||
|       _cacheState(); | ||||
|       return true; | ||||
|     } else { | ||||
| @@ -73,10 +76,10 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
|   } | ||||
|  | ||||
|   Future<bool> removeAssetFromAlbum( | ||||
|     String albumId, | ||||
|     List<String> assetIds, | ||||
|     Album album, | ||||
|     Iterable<Asset> assets, | ||||
|   ) async { | ||||
|     var res = await _albumService.removeAssetFromAlbum(albumId, assetIds); | ||||
|     var res = await _albumService.removeAssetFromAlbum(album, assets); | ||||
|  | ||||
|     if (res) { | ||||
|       return true; | ||||
| @@ -87,15 +90,15 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
| } | ||||
|  | ||||
| final sharedAlbumProvider = | ||||
|     StateNotifierProvider<SharedAlbumNotifier, List<AlbumResponseDto>>((ref) { | ||||
|     StateNotifierProvider<SharedAlbumNotifier, List<Album>>((ref) { | ||||
|   return SharedAlbumNotifier( | ||||
|     ref.watch(albumServiceProvider), | ||||
|     ref.watch(sharedAlbumCacheServiceProvider), | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| final sharedAlbumDetailProvider = FutureProvider.autoDispose | ||||
|     .family<AlbumResponseDto?, String>((ref, albumId) async { | ||||
| final sharedAlbumDetailProvider = | ||||
|     FutureProvider.autoDispose.family<Album?, String>((ref, albumId) async { | ||||
|   final AlbumService sharedAlbumService = ref.watch(albumServiceProvider); | ||||
|  | ||||
|   return await sharedAlbumService.getAlbumDetail(albumId); | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
| import 'package:immich_mobile/shared/services/user.service.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| final suggestedSharedUsersProvider = | ||||
|     FutureProvider.autoDispose<List<UserResponseDto>>((ref) async { | ||||
|     FutureProvider.autoDispose<List<User>>((ref) async { | ||||
|   UserService userService = ref.watch(userServiceProvider); | ||||
|  | ||||
|   return await userService.getAllUsersInfo(isAll: false) ?? []; | ||||
|   return await userService.getAllUsers(isAll: false) ?? []; | ||||
| }); | ||||
|   | ||||
| @@ -2,7 +2,9 @@ import 'dart:async'; | ||||
|  | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
| import 'package:immich_mobile/shared/providers/api.provider.dart'; | ||||
| import 'package:immich_mobile/shared/services/api.service.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| @@ -18,29 +20,31 @@ class AlbumService { | ||||
|  | ||||
|   AlbumService(this._apiService); | ||||
|  | ||||
|   Future<List<AlbumResponseDto>?> getAlbums({required bool isShared}) async { | ||||
|   Future<List<Album>?> getAlbums({required bool isShared}) async { | ||||
|     try { | ||||
|       return await _apiService.albumApi | ||||
|       final dto = await _apiService.albumApi | ||||
|           .getAllAlbums(shared: isShared ? isShared : null); | ||||
|       return dto?.map(Album.remote).toList(); | ||||
|     } catch (e) { | ||||
|       debugPrint("Error getAllSharedAlbum  ${e.toString()}"); | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<AlbumResponseDto?> createAlbum( | ||||
|   Future<Album?> createAlbum( | ||||
|     String albumName, | ||||
|     Iterable<Asset> assets, | ||||
|     List<String> sharedUserIds, | ||||
|   ) async { | ||||
|     Iterable<Asset> assets, [ | ||||
|     Iterable<User> sharedUsers = const [], | ||||
|   ]) async { | ||||
|     try { | ||||
|       return await _apiService.albumApi.createAlbum( | ||||
|       final dto = await _apiService.albumApi.createAlbum( | ||||
|         CreateAlbumDto( | ||||
|           albumName: albumName, | ||||
|           assetIds: assets.map((asset) => asset.id).toList(), | ||||
|           sharedWithUserIds: sharedUserIds, | ||||
|           assetIds: assets.map((asset) => asset.remoteId!).toList(), | ||||
|           sharedWithUserIds: sharedUsers.map((e) => e.id).toList(), | ||||
|         ), | ||||
|       ); | ||||
|       return dto != null ? Album.remote(dto) : null; | ||||
|     } catch (e) { | ||||
|       debugPrint("Error createSharedAlbum  ${e.toString()}"); | ||||
|       return null; | ||||
| @@ -50,14 +54,14 @@ class AlbumService { | ||||
|   /* | ||||
|    * Creates names like Untitled, Untitled (1), Untitled (2), ... | ||||
|    */ | ||||
|   String _getNextAlbumName(List<AlbumResponseDto>? albums) { | ||||
|   String _getNextAlbumName(List<Album>? albums) { | ||||
|     const baseName = "Untitled"; | ||||
|  | ||||
|     if (albums != null) { | ||||
|       for (int round = 0; round < albums.length; round++) { | ||||
|         final proposedName = "$baseName${round == 0 ? "" : " ($round)"}"; | ||||
|  | ||||
|         if (albums.where((a) => a.albumName == proposedName).isEmpty) { | ||||
|         if (albums.where((a) => a.name == proposedName).isEmpty) { | ||||
|           return proposedName; | ||||
|         } | ||||
|       } | ||||
| @@ -65,7 +69,7 @@ class AlbumService { | ||||
|     return baseName; | ||||
|   } | ||||
|  | ||||
|   Future<AlbumResponseDto?> createAlbumWithGeneratedName( | ||||
|   Future<Album?> createAlbumWithGeneratedName( | ||||
|     Iterable<Asset> assets, | ||||
|   ) async { | ||||
|     return createAlbum( | ||||
| @@ -75,9 +79,10 @@ class AlbumService { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<AlbumResponseDto?> getAlbumDetail(String albumId) async { | ||||
|   Future<Album?> getAlbumDetail(String albumId) async { | ||||
|     try { | ||||
|       return await _apiService.albumApi.getAlbumInfo(albumId); | ||||
|       final dto = await _apiService.albumApi.getAlbumInfo(albumId); | ||||
|       return dto != null ? Album.remote(dto) : null; | ||||
|     } catch (e) { | ||||
|       debugPrint('Error [getAlbumDetail] ${e.toString()}'); | ||||
|       return null; | ||||
| @@ -86,12 +91,12 @@ class AlbumService { | ||||
|  | ||||
|   Future<AddAssetsResponseDto?> addAdditionalAssetToAlbum( | ||||
|     Iterable<Asset> assets, | ||||
|     String albumId, | ||||
|     Album album, | ||||
|   ) async { | ||||
|     try { | ||||
|       var result = await _apiService.albumApi.addAssetsToAlbum( | ||||
|         albumId, | ||||
|         AddAssetsDto(assetIds: assets.map((asset) => asset.id).toList()), | ||||
|         album.remoteId!, | ||||
|         AddAssetsDto(assetIds: assets.map((asset) => asset.remoteId!).toList()), | ||||
|       ); | ||||
|       return result; | ||||
|     } catch (e) { | ||||
| @@ -102,11 +107,11 @@ class AlbumService { | ||||
|  | ||||
|   Future<bool> addAdditionalUserToAlbum( | ||||
|     List<String> sharedUserIds, | ||||
|     String albumId, | ||||
|     Album album, | ||||
|   ) async { | ||||
|     try { | ||||
|       var result = await _apiService.albumApi.addUsersToAlbum( | ||||
|         albumId, | ||||
|         album.remoteId!, | ||||
|         AddUsersDto(sharedUserIds: sharedUserIds), | ||||
|       ); | ||||
|  | ||||
| @@ -117,9 +122,9 @@ class AlbumService { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<bool> deleteAlbum(String albumId) async { | ||||
|   Future<bool> deleteAlbum(Album album) async { | ||||
|     try { | ||||
|       await _apiService.albumApi.deleteAlbum(albumId); | ||||
|       await _apiService.albumApi.deleteAlbum(album.remoteId!); | ||||
|       return true; | ||||
|     } catch (e) { | ||||
|       debugPrint("Error deleteAlbum  ${e.toString()}"); | ||||
| @@ -127,10 +132,9 @@ class AlbumService { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<bool> leaveAlbum(String albumId) async { | ||||
|   Future<bool> leaveAlbum(Album album) async { | ||||
|     try { | ||||
|       await _apiService.albumApi.removeUserFromAlbum(albumId, "me"); | ||||
|  | ||||
|       await _apiService.albumApi.removeUserFromAlbum(album.remoteId!, "me"); | ||||
|       return true; | ||||
|     } catch (e) { | ||||
|       debugPrint("Error deleteAlbum  ${e.toString()}"); | ||||
| @@ -139,13 +143,15 @@ class AlbumService { | ||||
|   } | ||||
|  | ||||
|   Future<bool> removeAssetFromAlbum( | ||||
|     String albumId, | ||||
|     List<String> assetIds, | ||||
|     Album album, | ||||
|     Iterable<Asset> assets, | ||||
|   ) async { | ||||
|     try { | ||||
|       await _apiService.albumApi.removeAssetFromAlbum( | ||||
|         albumId, | ||||
|         RemoveAssetsDto(assetIds: assetIds), | ||||
|         album.remoteId!, | ||||
|         RemoveAssetsDto( | ||||
|           assetIds: assets.map((e) => e.remoteId!).toList(growable: false), | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
|       return true; | ||||
| @@ -156,17 +162,17 @@ class AlbumService { | ||||
|   } | ||||
|  | ||||
|   Future<bool> changeTitleAlbum( | ||||
|     String albumId, | ||||
|     String ownerId, | ||||
|     Album album, | ||||
|     String newAlbumTitle, | ||||
|   ) async { | ||||
|     try { | ||||
|       await _apiService.albumApi.updateAlbumInfo( | ||||
|         albumId, | ||||
|         album.remoteId!, | ||||
|         UpdateAlbumDto( | ||||
|           albumName: newAlbumTitle, | ||||
|         ), | ||||
|       ); | ||||
|       album.name = newAlbumTitle; | ||||
|  | ||||
|       return true; | ||||
|     } catch (e) { | ||||
|   | ||||
| @@ -1,32 +1,30 @@ | ||||
|  | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/services/json_cache.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class BaseAlbumCacheService extends JsonCache<List<AlbumResponseDto>> { | ||||
| class BaseAlbumCacheService extends JsonCache<List<Album>> { | ||||
|   BaseAlbumCacheService(super.cacheFileName); | ||||
|  | ||||
|   @override | ||||
|   void put(List<AlbumResponseDto> data) { | ||||
|   void put(List<Album> data) { | ||||
|     putRawData(data.map((e) => e.toJson()).toList()); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<List<AlbumResponseDto>> get() async { | ||||
|   Future<List<Album>?> get() async { | ||||
|     try { | ||||
|       final mapList = await readRawData() as List<dynamic>; | ||||
|  | ||||
|       final responseData = mapList | ||||
|           .map((e) => AlbumResponseDto.fromJson(e)) | ||||
|           .whereNotNull() | ||||
|           .toList(); | ||||
|       final responseData = | ||||
|           mapList.map((e) => Album.fromJson(e)).whereNotNull().toList(); | ||||
|  | ||||
|       return responseData; | ||||
|     } catch (e) { | ||||
|       await invalidate(); | ||||
|       debugPrint(e.toString()); | ||||
|       return []; | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -40,10 +38,9 @@ class SharedAlbumCacheService extends BaseAlbumCacheService { | ||||
| } | ||||
|  | ||||
| final albumCacheServiceProvider = Provider( | ||||
|       (ref) => AlbumCacheService(), | ||||
|   (ref) => AlbumCacheService(), | ||||
| ); | ||||
|  | ||||
| final sharedAlbumCacheServiceProvider = Provider( | ||||
|       (ref) => SharedAlbumCacheService(), | ||||
|   (ref) => SharedAlbumCacheService(), | ||||
| ); | ||||
|  | ||||
|   | ||||
| @@ -8,10 +8,10 @@ import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart | ||||
| import 'package:immich_mobile/modules/album/services/album.service.dart'; | ||||
| import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/ui/drag_sheet.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_toast.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class AddToAlbumBottomSheet extends HookConsumerWidget { | ||||
|   /// The asset to add to an album | ||||
| @@ -39,22 +39,22 @@ class AddToAlbumBottomSheet extends HookConsumerWidget { | ||||
|       [], | ||||
|     ); | ||||
|  | ||||
|     void addToAlbum(AlbumResponseDto album) async { | ||||
|     void addToAlbum(Album album) async { | ||||
|       final result = await albumService.addAdditionalAssetToAlbum( | ||||
|         assets, | ||||
|         album.id, | ||||
|         album, | ||||
|       ); | ||||
|  | ||||
|       if (result != null) { | ||||
|         if (result.alreadyInAlbum.isNotEmpty) { | ||||
|           ImmichToast.show( | ||||
|             context: context, | ||||
|             msg: 'Already in ${album.albumName}', | ||||
|             msg: 'Already in ${album.name}', | ||||
|           ); | ||||
|         } else { | ||||
|           ImmichToast.show( | ||||
|             context: context, | ||||
|             msg: 'Added to ${album.albumName}', | ||||
|             msg: 'Added to ${album.name}', | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|   | ||||
| @@ -1,14 +1,13 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
|  | ||||
| class AddToAlbumSliverList extends HookConsumerWidget { | ||||
|  | ||||
|   /// The asset to add to an album | ||||
|   final List<AlbumResponseDto> albums; | ||||
|   final List<AlbumResponseDto> sharedAlbums; | ||||
|   final void Function(AlbumResponseDto) onAddToAlbum; | ||||
|   final List<Album> albums; | ||||
|   final List<Album> sharedAlbums; | ||||
|   final void Function(Album) onAddToAlbum; | ||||
|  | ||||
|   const AddToAlbumSliverList({ | ||||
|     Key? key, | ||||
| @@ -21,36 +20,36 @@ class AddToAlbumSliverList extends HookConsumerWidget { | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     return SliverList( | ||||
|       delegate: SliverChildBuilderDelegate( | ||||
|         childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1), | ||||
|         (context, index) { | ||||
|           // Build shared expander | ||||
|           if (index == 0 && sharedAlbums.isNotEmpty) { | ||||
|               return Padding( | ||||
|                 padding: const EdgeInsets.only(bottom: 8), | ||||
|                 child: ExpansionTile( | ||||
|                   title: const Text('Shared'), | ||||
|                   tilePadding: const EdgeInsets.symmetric(horizontal: 10.0), | ||||
|                   leading: const Icon(Icons.group), | ||||
|                   children: sharedAlbums.map((album) =>  | ||||
|                     AlbumThumbnailListTile( | ||||
|           childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1), | ||||
|           (context, index) { | ||||
|         // Build shared expander | ||||
|         if (index == 0 && sharedAlbums.isNotEmpty) { | ||||
|           return Padding( | ||||
|             padding: const EdgeInsets.only(bottom: 8), | ||||
|             child: ExpansionTile( | ||||
|               title: const Text('Shared'), | ||||
|               tilePadding: const EdgeInsets.symmetric(horizontal: 10.0), | ||||
|               leading: const Icon(Icons.group), | ||||
|               children: sharedAlbums | ||||
|                   .map( | ||||
|                     (album) => AlbumThumbnailListTile( | ||||
|                       album: album, | ||||
|                       onTap: () => onAddToAlbum(album), | ||||
|                     ), | ||||
|                   ).toList(), | ||||
|                 ), | ||||
|               ); | ||||
|           } | ||||
|  | ||||
|           // Build albums list | ||||
|           final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0); | ||||
|           final album = albums[offset]; | ||||
|           return AlbumThumbnailListTile( | ||||
|             album: album, | ||||
|             onTap: () => onAddToAlbum(album), | ||||
|                   ) | ||||
|                   .toList(), | ||||
|             ), | ||||
|           ); | ||||
|         } | ||||
|       ), | ||||
|  | ||||
|         // Build albums list | ||||
|         final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0); | ||||
|         final album = albums[offset]; | ||||
|         return AlbumThumbnailListTile( | ||||
|           album: album, | ||||
|           onTap: () => onAddToAlbum(album), | ||||
|         ); | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:hive/hive.dart'; | ||||
| import 'package:immich_mobile/constants/hive_box.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/utils/image_url_builder.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| @@ -14,7 +15,7 @@ class AlbumThumbnailCard extends StatelessWidget { | ||||
|     required this.album, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   final AlbumResponseDto album; | ||||
|   final Album album; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -72,7 +73,7 @@ class AlbumThumbnailCard extends StatelessWidget { | ||||
|               child: SizedBox( | ||||
|                 width: cardSize, | ||||
|                 child: Text( | ||||
|                   album.albumName, | ||||
|                   album.name, | ||||
|                   style: const TextStyle( | ||||
|                     fontWeight: FontWeight.bold, | ||||
|                   ), | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:hive/hive.dart'; | ||||
| import 'package:immich_mobile/constants/hive_box.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/utils/image_url_builder.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| @@ -15,7 +16,7 @@ class AlbumThumbnailListTile extends StatelessWidget { | ||||
|     this.onTap, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   final AlbumResponseDto album; | ||||
|   final Album album; | ||||
|   final void Function()? onTap; | ||||
|  | ||||
|   @override | ||||
| @@ -80,7 +81,7 @@ class AlbumThumbnailListTile extends StatelessWidget { | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   Text( | ||||
|                     album.albumName, | ||||
|                     album.name, | ||||
|                     style: const TextStyle( | ||||
|                       fontWeight: FontWeight.bold, | ||||
|                     ), | ||||
|   | ||||
| @@ -9,21 +9,19 @@ import 'package:immich_mobile/modules/album/providers/asset_selection.provider.d | ||||
| import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; | ||||
| import 'package:immich_mobile/modules/album/services/album.service.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_toast.dart'; | ||||
| import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|   const AlbumViewerAppbar({ | ||||
|     Key? key, | ||||
|     required this.albumInfo, | ||||
|     required this.album, | ||||
|     required this.userId, | ||||
|     required this.albumId, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   final AlbumResponseDto albumInfo; | ||||
|   final Album album; | ||||
|   final String userId; | ||||
|   final String albumId; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
| @@ -34,19 +32,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|     final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText; | ||||
|     final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum; | ||||
|  | ||||
|     void onDeleteAlbumPressed(String albumId) async { | ||||
|     void onDeleteAlbumPressed() async { | ||||
|       ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|       bool isSuccess = | ||||
|           await ref.watch(albumServiceProvider).deleteAlbum(albumId); | ||||
|       bool isSuccess = await ref.watch(albumServiceProvider).deleteAlbum(album); | ||||
|  | ||||
|       if (isSuccess) { | ||||
|         if (albumInfo.shared) { | ||||
|           ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId); | ||||
|         if (album.shared) { | ||||
|           ref.watch(sharedAlbumProvider.notifier).deleteAlbum(album); | ||||
|           AutoRouter.of(context) | ||||
|               .navigate(const TabControllerRoute(children: [SharingRoute()])); | ||||
|         } else { | ||||
|           ref.watch(albumProvider.notifier).deleteAlbum(albumId); | ||||
|           ref.watch(albumProvider.notifier).deleteAlbum(album); | ||||
|           AutoRouter.of(context) | ||||
|               .navigate(const TabControllerRoute(children: [LibraryRoute()])); | ||||
|         } | ||||
| @@ -62,11 +59,11 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|       ImmichLoadingOverlayController.appLoader.hide(); | ||||
|     } | ||||
|  | ||||
|     void onLeaveAlbumPressed(String albumId) async { | ||||
|     void onLeaveAlbumPressed() async { | ||||
|       ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|       bool isSuccess = | ||||
|           await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId); | ||||
|           await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(album); | ||||
|  | ||||
|       if (isSuccess) { | ||||
|         AutoRouter.of(context) | ||||
| @@ -84,20 +81,20 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|       ImmichLoadingOverlayController.appLoader.hide(); | ||||
|     } | ||||
|  | ||||
|     void onRemoveFromAlbumPressed(String albumId) async { | ||||
|     void onRemoveFromAlbumPressed() async { | ||||
|       ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|       bool isSuccess = | ||||
|           await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum( | ||||
|                 albumId, | ||||
|                 selectedAssetsInAlbum.map((a) => a.id).toList(), | ||||
|                 album, | ||||
|                 selectedAssetsInAlbum, | ||||
|               ); | ||||
|  | ||||
|       if (isSuccess) { | ||||
|         Navigator.pop(context); | ||||
|         ref.watch(assetSelectionProvider.notifier).disableMultiselection(); | ||||
|         ref.watch(albumProvider.notifier).getAllAlbums(); | ||||
|         ref.invalidate(sharedAlbumDetailProvider(albumId)); | ||||
|         ref.invalidate(sharedAlbumDetailProvider(album.id)); | ||||
|       } else { | ||||
|         Navigator.pop(context); | ||||
|         ImmichToast.show( | ||||
| @@ -113,27 +110,27 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|  | ||||
|     buildBottomSheetActionButton() { | ||||
|       if (isMultiSelectionEnable) { | ||||
|         if (albumInfo.ownerId == userId) { | ||||
|         if (album.ownerId == userId) { | ||||
|           return ListTile( | ||||
|             leading: const Icon(Icons.delete_sweep_rounded), | ||||
|             title: const Text( | ||||
|               'album_viewer_appbar_share_remove', | ||||
|               style: TextStyle(fontWeight: FontWeight.bold), | ||||
|             ).tr(), | ||||
|             onTap: () => onRemoveFromAlbumPressed(albumId), | ||||
|             onTap: () => onRemoveFromAlbumPressed(), | ||||
|           ); | ||||
|         } else { | ||||
|           return const SizedBox(); | ||||
|         } | ||||
|       } else { | ||||
|         if (albumInfo.ownerId == userId) { | ||||
|         if (album.ownerId == userId) { | ||||
|           return ListTile( | ||||
|             leading: const Icon(Icons.delete_forever_rounded), | ||||
|             title: const Text( | ||||
|               'album_viewer_appbar_share_delete', | ||||
|               style: TextStyle(fontWeight: FontWeight.bold), | ||||
|             ).tr(), | ||||
|             onTap: () => onDeleteAlbumPressed(albumId), | ||||
|             onTap: () => onDeleteAlbumPressed(), | ||||
|           ); | ||||
|         } else { | ||||
|           return ListTile( | ||||
| @@ -142,7 +139,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|               'album_viewer_appbar_share_leave', | ||||
|               style: TextStyle(fontWeight: FontWeight.bold), | ||||
|             ).tr(), | ||||
|             onTap: () => onLeaveAlbumPressed(albumId), | ||||
|             onTap: () => onLeaveAlbumPressed(), | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
| @@ -180,7 +177,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|           onPressed: () async { | ||||
|             bool isSuccess = await ref | ||||
|                 .watch(albumViewerProvider.notifier) | ||||
|                 .changeAlbumTitle(albumId, userId, newAlbumTitle); | ||||
|                 .changeAlbumTitle(album, newAlbumTitle); | ||||
|  | ||||
|             if (!isSuccess) { | ||||
|               ImmichToast.show( | ||||
|   | ||||
| @@ -3,21 +3,20 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/providers/album_viewer.provider.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
|  | ||||
| class AlbumViewerEditableTitle extends HookConsumerWidget { | ||||
|   final AlbumResponseDto albumInfo; | ||||
|   final Album album; | ||||
|   final FocusNode titleFocusNode; | ||||
|   const AlbumViewerEditableTitle({ | ||||
|     Key? key, | ||||
|     required this.albumInfo, | ||||
|     required this.album, | ||||
|     required this.titleFocusNode, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final titleTextEditController = | ||||
|         useTextEditingController(text: albumInfo.albumName); | ||||
|     final titleTextEditController = useTextEditingController(text: album.name); | ||||
|     final isDarkTheme = Theme.of(context).brightness == Brightness.dark; | ||||
|  | ||||
|     void onFocusModeChange() { | ||||
| @@ -50,9 +49,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget { | ||||
|       onTap: () { | ||||
|         FocusScope.of(context).requestFocus(titleFocusNode); | ||||
|  | ||||
|         ref | ||||
|             .watch(albumViewerProvider.notifier) | ||||
|             .setEditTitleText(albumInfo.albumName); | ||||
|         ref.watch(albumViewerProvider.notifier).setEditTitleText(album.name); | ||||
|         ref.watch(albumViewerProvider.notifier).enableEditAlbum(); | ||||
|  | ||||
|         if (titleTextEditController.text == 'Untitled') { | ||||
|   | ||||
| @@ -19,11 +19,10 @@ import 'package:immich_mobile/modules/album/ui/album_viewer_thumbnail.dart'; | ||||
| import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; | ||||
| import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart'; | ||||
| import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class AlbumViewerPage extends HookConsumerWidget { | ||||
|   final String albumId; | ||||
| @@ -34,16 +33,16 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     FocusNode titleFocusNode = useFocusNode(); | ||||
|     ScrollController scrollController = useScrollController(); | ||||
|     var albumInfo = ref.watch(sharedAlbumDetailProvider(albumId)); | ||||
|     final album = ref.watch(sharedAlbumDetailProvider(albumId)); | ||||
|  | ||||
|     final userId = ref.watch(authenticationProvider).userId; | ||||
|  | ||||
|     /// Find out if the assets in album exist on the device | ||||
|     /// If they exist, add to selected asset state to show they are already selected. | ||||
|     void onAddPhotosPressed(AlbumResponseDto albumInfo) async { | ||||
|     void onAddPhotosPressed(Album albumInfo) async { | ||||
|       if (albumInfo.assets.isNotEmpty == true) { | ||||
|         ref.watch(assetSelectionProvider.notifier).addNewAssets( | ||||
|               albumInfo.assets.map((e) => Asset.remote(e)).toList(), | ||||
|               albumInfo.assets, | ||||
|             ); | ||||
|       } | ||||
|  | ||||
| @@ -60,7 +59,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|           var addAssetsResult = | ||||
|               await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum( | ||||
|                     returnPayload.selectedAdditionalAsset, | ||||
|                     albumId, | ||||
|                     albumInfo, | ||||
|                   ); | ||||
|  | ||||
|           if (addAssetsResult != null && | ||||
| @@ -78,10 +77,10 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     void onAddUsersPressed(AlbumResponseDto albumInfo) async { | ||||
|     void onAddUsersPressed(Album album) async { | ||||
|       List<String>? sharedUserIds = | ||||
|           await AutoRouter.of(context).push<List<String>?>( | ||||
|         SelectAdditionalUserForSharingRoute(albumInfo: albumInfo), | ||||
|         SelectAdditionalUserForSharingRoute(album: album), | ||||
|       ); | ||||
|  | ||||
|       if (sharedUserIds != null) { | ||||
| @@ -89,7 +88,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|  | ||||
|         var isSuccess = await ref | ||||
|             .watch(albumServiceProvider) | ||||
|             .addAdditionalUserToAlbum(sharedUserIds, albumId); | ||||
|             .addAdditionalUserToAlbum(sharedUserIds, album); | ||||
|  | ||||
|         if (isSuccess) { | ||||
|           ref.invalidate(sharedAlbumDetailProvider(albumId)); | ||||
| @@ -99,18 +98,18 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Widget buildTitle(AlbumResponseDto albumInfo) { | ||||
|     Widget buildTitle(Album album) { | ||||
|       return Padding( | ||||
|         padding: const EdgeInsets.only(left: 8, right: 8, top: 16), | ||||
|         child: userId == albumInfo.ownerId | ||||
|         child: userId == album.ownerId | ||||
|             ? AlbumViewerEditableTitle( | ||||
|                 albumInfo: albumInfo, | ||||
|                 album: album, | ||||
|                 titleFocusNode: titleFocusNode, | ||||
|               ) | ||||
|             : Padding( | ||||
|                 padding: const EdgeInsets.only(left: 8.0), | ||||
|                 child: Text( | ||||
|                   albumInfo.albumName, | ||||
|                   album.name, | ||||
|                   style: const TextStyle( | ||||
|                     fontSize: 24, | ||||
|                     fontWeight: FontWeight.bold, | ||||
| @@ -120,30 +119,22 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     Widget buildAlbumDateRange(AlbumResponseDto albumInfo) { | ||||
|       String startDate = ""; | ||||
|       DateTime parsedStartDate = | ||||
|           DateTime.parse(albumInfo.assets.first.createdAt); | ||||
|       DateTime parsedEndDate = DateTime.parse( | ||||
|         albumInfo.assets.last.createdAt, | ||||
|       ); //Need default. | ||||
|  | ||||
|       if (parsedStartDate.year == parsedEndDate.year) { | ||||
|         startDate = DateFormat('LLL d').format(parsedStartDate); | ||||
|       } else { | ||||
|         startDate = DateFormat('LLL d, y').format(parsedStartDate); | ||||
|       } | ||||
|  | ||||
|       String endDate = DateFormat('LLL d, y').format(parsedEndDate); | ||||
|     Widget buildAlbumDateRange(Album album) { | ||||
|       final DateTime startDate = album.assets.first.createdAt; | ||||
|       final DateTime endDate = album.assets.last.createdAt; //Need default. | ||||
|       final String startDateText = | ||||
|           DateFormat(startDate.year == endDate.year ? 'LLL d' : 'LLL d, y') | ||||
|               .format(startDate); | ||||
|       final String endDateText = DateFormat('LLL d, y').format(endDate); | ||||
|  | ||||
|       return Padding( | ||||
|         padding: EdgeInsets.only( | ||||
|           left: 16.0, | ||||
|           top: 8.0, | ||||
|           bottom: albumInfo.shared ? 0.0 : 8.0, | ||||
|           bottom: album.shared ? 0.0 : 8.0, | ||||
|         ), | ||||
|         child: Text( | ||||
|           "$startDate-$endDate", | ||||
|           "$startDateText-$endDateText", | ||||
|           style: const TextStyle( | ||||
|             fontSize: 14, | ||||
|             fontWeight: FontWeight.bold, | ||||
| @@ -153,15 +144,14 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     Widget buildHeader(AlbumResponseDto albumInfo) { | ||||
|     Widget buildHeader(Album album) { | ||||
|       return SliverToBoxAdapter( | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           children: [ | ||||
|             buildTitle(albumInfo), | ||||
|             if (albumInfo.assets.isNotEmpty == true) | ||||
|               buildAlbumDateRange(albumInfo), | ||||
|             if (albumInfo.shared) | ||||
|             buildTitle(album), | ||||
|             if (album.assets.isNotEmpty == true) buildAlbumDateRange(album), | ||||
|             if (album.shared) | ||||
|               SizedBox( | ||||
|                 height: 60, | ||||
|                 child: ListView.builder( | ||||
| @@ -185,7 +175,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|                       ), | ||||
|                     ); | ||||
|                   }), | ||||
|                   itemCount: albumInfo.sharedUsers.length, | ||||
|                   itemCount: album.sharedUsers.length, | ||||
|                 ), | ||||
|               ) | ||||
|           ], | ||||
| @@ -193,12 +183,12 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     Widget buildImageGrid(AlbumResponseDto albumInfo) { | ||||
|     Widget buildImageGrid(Album album) { | ||||
|       final appSettingService = ref.watch(appSettingsServiceProvider); | ||||
|       final bool showStorageIndicator = | ||||
|           appSettingService.getSetting(AppSettingsEnum.storageIndicator); | ||||
|  | ||||
|       if (albumInfo.assets.isNotEmpty) { | ||||
|       if (album.assets.isNotEmpty) { | ||||
|         return SliverPadding( | ||||
|           padding: const EdgeInsets.only(top: 10.0), | ||||
|           sliver: SliverGrid( | ||||
| @@ -211,13 +201,12 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|             delegate: SliverChildBuilderDelegate( | ||||
|               (BuildContext context, int index) { | ||||
|                 return AlbumViewerThumbnail( | ||||
|                   asset: Asset.remote(albumInfo.assets[index]), | ||||
|                   assetList: | ||||
|                       albumInfo.assets.map((e) => Asset.remote(e)).toList(), | ||||
|                   asset: album.assets[index], | ||||
|                   assetList: album.assets, | ||||
|                   showStorageIndicator: showStorageIndicator, | ||||
|                 ); | ||||
|               }, | ||||
|               childCount: albumInfo.assetCount, | ||||
|               childCount: album.assetCount, | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
| @@ -225,7 +214,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|       return const SliverToBoxAdapter(); | ||||
|     } | ||||
|  | ||||
|     Widget buildControlButton(AlbumResponseDto albumInfo) { | ||||
|     Widget buildControlButton(Album album) { | ||||
|       return Padding( | ||||
|         padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8), | ||||
|         child: SizedBox( | ||||
| @@ -235,13 +224,13 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|             children: [ | ||||
|               AlbumActionOutlinedButton( | ||||
|                 iconData: Icons.add_photo_alternate_outlined, | ||||
|                 onPressed: () => onAddPhotosPressed(albumInfo), | ||||
|                 onPressed: () => onAddPhotosPressed(album), | ||||
|                 labelText: "share_add_photos".tr(), | ||||
|               ), | ||||
|               if (userId == albumInfo.ownerId) | ||||
|               if (userId == album.ownerId) | ||||
|                 AlbumActionOutlinedButton( | ||||
|                   iconData: Icons.person_add_alt_rounded, | ||||
|                   onPressed: () => onAddUsersPressed(albumInfo), | ||||
|                   onPressed: () => onAddUsersPressed(album), | ||||
|                   labelText: "album_viewer_page_share_add_users".tr(), | ||||
|                 ), | ||||
|             ], | ||||
| @@ -251,7 +240,10 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     Future<bool> onWillPop() async { | ||||
|       final isMultiselectEnable = ref.read(assetSelectionProvider).selectedAssetsInAlbumViewer.isNotEmpty; | ||||
|       final isMultiselectEnable = ref | ||||
|           .read(assetSelectionProvider) | ||||
|           .selectedAssetsInAlbumViewer | ||||
|           .isNotEmpty; | ||||
|       if (isMultiselectEnable) { | ||||
|         ref.watch(assetSelectionProvider.notifier).removeAll(); | ||||
|         return false; | ||||
| @@ -260,7 +252,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     Widget buildBody(AlbumResponseDto albumInfo) { | ||||
|     Widget buildBody(Album album) { | ||||
|       return WillPopScope( | ||||
|         onWillPop: onWillPop, | ||||
|         child: GestureDetector( | ||||
| @@ -274,7 +266,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|             child: CustomScrollView( | ||||
|               controller: scrollController, | ||||
|               slivers: [ | ||||
|                 buildHeader(albumInfo), | ||||
|                 buildHeader(album), | ||||
|                 SliverPersistentHeader( | ||||
|                   pinned: true, | ||||
|                   delegate: ImmichSliverPersistentAppBarDelegate( | ||||
| @@ -282,11 +274,11 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|                     maxHeight: 50, | ||||
|                     child: Container( | ||||
|                       color: Theme.of(context).scaffoldBackgroundColor, | ||||
|                       child: buildControlButton(albumInfo), | ||||
|                       child: buildControlButton(album), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|                 buildImageGrid(albumInfo) | ||||
|                 buildImageGrid(album) | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
| @@ -295,13 +287,12 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|       appBar: albumInfo.when( | ||||
|         data: (AlbumResponseDto? data) { | ||||
|       appBar: album.when( | ||||
|         data: (Album? data) { | ||||
|           if (data != null) { | ||||
|             return AlbumViewerAppbar( | ||||
|               albumInfo: data, | ||||
|               album: data, | ||||
|               userId: userId, | ||||
|               albumId: albumId, | ||||
|             ); | ||||
|           } | ||||
|           return null; | ||||
| @@ -309,7 +300,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|         error: (e, _) => null, | ||||
|         loading: () => null, | ||||
|       ), | ||||
|       body: albumInfo.when( | ||||
|       body: album.when( | ||||
|         data: (albumInfo) => albumInfo != null | ||||
|             ? buildBody(albumInfo) | ||||
|             : const Center( | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/providers/album.provider.dart'; | ||||
| import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
|  | ||||
| class LibraryPage extends HookConsumerWidget { | ||||
|   const LibraryPage({Key? key}) : super(key: key); | ||||
| @@ -41,11 +41,11 @@ class LibraryPage extends HookConsumerWidget { | ||||
|  | ||||
|     final selectedAlbumSortOrder = useState(0); | ||||
|  | ||||
|     List<AlbumResponseDto> sortedAlbums() { | ||||
|     List<Album> sortedAlbums() { | ||||
|       if (selectedAlbumSortOrder.value == 0) { | ||||
|         return albums.sortedBy((album) => album.createdAt).reversed.toList(); | ||||
|       } | ||||
|       return albums.sortedBy((album) => album.albumName); | ||||
|       return albums.sortedBy((album) => album.name); | ||||
|     } | ||||
|  | ||||
|     Widget buildSortButton() { | ||||
|   | ||||
| @@ -4,27 +4,28 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class SelectAdditionalUserForSharingPage extends HookConsumerWidget { | ||||
|   final AlbumResponseDto albumInfo; | ||||
|   final Album album; | ||||
|  | ||||
|   const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo}) | ||||
|   const SelectAdditionalUserForSharingPage({Key? key, required this.album}) | ||||
|       : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     AsyncValue<List<UserResponseDto>> suggestedShareUsers = | ||||
|     final AsyncValue<List<User>> suggestedShareUsers = | ||||
|         ref.watch(suggestedSharedUsersProvider); | ||||
|     final sharedUsersList = useState<Set<UserResponseDto>>({}); | ||||
|     final sharedUsersList = useState<Set<User>>({}); | ||||
|  | ||||
|     addNewUsersHandler() { | ||||
|       AutoRouter.of(context) | ||||
|           .pop(sharedUsersList.value.map((e) => e.id).toList()); | ||||
|     } | ||||
|  | ||||
|     buildTileIcon(UserResponseDto user) { | ||||
|     buildTileIcon(User user) { | ||||
|       if (sharedUsersList.value.contains(user)) { | ||||
|         return CircleAvatar( | ||||
|           backgroundColor: Theme.of(context).primaryColor, | ||||
| @@ -42,7 +43,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     buildUserList(List<UserResponseDto> users) { | ||||
|     buildUserList(List<User> users) { | ||||
|       List<Widget> usersChip = []; | ||||
|  | ||||
|       for (var user in sharedUsersList.value) { | ||||
| @@ -140,9 +141,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { | ||||
|       ), | ||||
|       body: suggestedShareUsers.when( | ||||
|         data: (users) { | ||||
|           for (var sharedUsers in albumInfo.sharedUsers) { | ||||
|           for (var sharedUsers in album.sharedUsers) { | ||||
|             users.removeWhere( | ||||
|               (u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId, | ||||
|               (u) => u.id == sharedUsers.id || u.id == album.ownerId, | ||||
|             ); | ||||
|           } | ||||
|  | ||||
|   | ||||
| @@ -8,16 +8,16 @@ import 'package:immich_mobile/modules/album/providers/asset_selection.provider.d | ||||
| import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; | ||||
| import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|   const SelectUserForSharingPage({Key? key}) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final sharedUsersList = useState<Set<UserResponseDto>>({}); | ||||
|     AsyncValue<List<UserResponseDto>> suggestedShareUsers = | ||||
|     final sharedUsersList = useState<Set<User>>({}); | ||||
|     AsyncValue<List<User>> suggestedShareUsers = | ||||
|         ref.watch(suggestedSharedUsersProvider); | ||||
|  | ||||
|     createSharedAlbum() async { | ||||
| @@ -25,7 +25,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|           await ref.watch(sharedAlbumProvider.notifier).createSharedAlbum( | ||||
|                 ref.watch(albumTitleProvider), | ||||
|                 ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum, | ||||
|                 sharedUsersList.value.map((userInfo) => userInfo.id).toList(), | ||||
|                 sharedUsersList.value, | ||||
|               ); | ||||
|  | ||||
|       if (newAlbum != null) { | ||||
| @@ -44,7 +44,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     buildTileIcon(UserResponseDto user) { | ||||
|     buildTileIcon(User user) { | ||||
|       if (sharedUsersList.value.contains(user)) { | ||||
|         return CircleAvatar( | ||||
|           backgroundColor: Theme.of(context).primaryColor, | ||||
| @@ -62,7 +62,7 @@ class SelectUserForSharingPage extends HookConsumerWidget { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     buildUserList(List<UserResponseDto> users) { | ||||
|     buildUserList(List<User> users) { | ||||
|       List<Widget> usersChip = []; | ||||
|  | ||||
|       for (var user in sharedUsersList.value) { | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import 'package:immich_mobile/constants/hive_box.dart'; | ||||
| import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; | ||||
| import 'package:immich_mobile/modules/album/ui/sharing_sliver_appbar.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/utils/image_url_builder.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class SharingPage extends HookConsumerWidget { | ||||
|   const SharingPage({Key? key}) : super(key: key); | ||||
| @@ -18,7 +18,7 @@ class SharingPage extends HookConsumerWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     var box = Hive.box(userInfoBox); | ||||
|     final List<AlbumResponseDto> sharedAlbums = ref.watch(sharedAlbumProvider); | ||||
|     final List<Album> sharedAlbums = ref.watch(sharedAlbumProvider); | ||||
|  | ||||
|     useEffect( | ||||
|       () { | ||||
| @@ -52,7 +52,7 @@ class SharingPage extends HookConsumerWidget { | ||||
|                 ), | ||||
|               ), | ||||
|               title: Text( | ||||
|                 sharedAlbums[index].albumName, | ||||
|                 sharedAlbums[index].name, | ||||
|                 maxLines: 1, | ||||
|                 overflow: TextOverflow.ellipsis, | ||||
|                 style: Theme.of(context).textTheme.bodyMedium?.copyWith( | ||||
|   | ||||
| @@ -4,16 +4,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart'; | ||||
| import 'package:immich_mobile/shared/ui/drag_sheet.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
|  | ||||
| class ControlBottomAppBar extends ConsumerWidget { | ||||
|   final Function onShare; | ||||
|   final Function onDelete; | ||||
|   final Function(AlbumResponseDto album) onAddToAlbum; | ||||
|   final Function(Album album) onAddToAlbum; | ||||
|   final void Function() onCreateNewAlbum; | ||||
|  | ||||
|   final List<AlbumResponseDto> albums; | ||||
|   final List<AlbumResponseDto> sharedAlbums; | ||||
|   final List<Album> albums; | ||||
|   final List<Album> sharedAlbums; | ||||
|  | ||||
|   const ControlBottomAppBar({ | ||||
|     Key? key, | ||||
|   | ||||
| @@ -18,6 +18,7 @@ import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart | ||||
| import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; | ||||
| import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/providers/asset.provider.dart'; | ||||
| import 'package:immich_mobile/shared/providers/server_info.provider.dart'; | ||||
| @@ -25,7 +26,6 @@ import 'package:immich_mobile/shared/providers/websocket.provider.dart'; | ||||
| import 'package:immich_mobile/shared/services/share.service.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_toast.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class HomePage extends HookConsumerWidget { | ||||
|   const HomePage({Key? key}) : super(key: key); | ||||
| @@ -102,14 +102,14 @@ class HomePage extends HookConsumerWidget { | ||||
|         return assets; | ||||
|       } | ||||
|  | ||||
|       void onAddToAlbum(AlbumResponseDto album) async { | ||||
|       void onAddToAlbum(Album album) async { | ||||
|         final Iterable<Asset> assets = remoteOnlySelection(); | ||||
|         if (assets.isEmpty) { | ||||
|           return; | ||||
|         } | ||||
|         final result = await albumService.addAdditionalAssetToAlbum( | ||||
|           assets, | ||||
|           album.id, | ||||
|           album, | ||||
|         ); | ||||
|  | ||||
|         if (result != null) { | ||||
| @@ -118,7 +118,7 @@ class HomePage extends HookConsumerWidget { | ||||
|               context: context, | ||||
|               msg: "home_page_add_to_album_conflicts".tr( | ||||
|                 namedArgs: { | ||||
|                   "album": album.albumName, | ||||
|                   "album": album.name, | ||||
|                   "added": result.successfullyAdded.toString(), | ||||
|                   "failed": result.alreadyInAlbum.length.toString() | ||||
|                 }, | ||||
| @@ -129,7 +129,7 @@ class HomePage extends HookConsumerWidget { | ||||
|               context: context, | ||||
|               msg: "home_page_add_to_album_success".tr( | ||||
|                 namedArgs: { | ||||
|                   "album": album.albumName, | ||||
|                   "album": album.name, | ||||
|                   "added": result.successfullyAdded.toString(), | ||||
|                 }, | ||||
|               ), | ||||
|   | ||||
| @@ -24,12 +24,12 @@ import 'package:immich_mobile/modules/search/views/search_result_page.dart'; | ||||
| import 'package:immich_mobile/modules/settings/views/settings_page.dart'; | ||||
| import 'package:immich_mobile/routing/auth_guard.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/providers/api.provider.dart'; | ||||
| import 'package:immich_mobile/shared/services/api.service.dart'; | ||||
| import 'package:immich_mobile/shared/views/app_log_page.dart'; | ||||
| import 'package:immich_mobile/shared/views/splash_screen.dart'; | ||||
| import 'package:immich_mobile/shared/views/tab_controller_page.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:photo_manager/photo_manager.dart'; | ||||
|  | ||||
| part 'router.gr.dart'; | ||||
|   | ||||
| @@ -108,7 +108,7 @@ class _$AppRouter extends RootStackRouter { | ||||
|       return CustomPage<List<String>?>( | ||||
|           routeData: routeData, | ||||
|           child: SelectAdditionalUserForSharingPage( | ||||
|               key: args.key, albumInfo: args.albumInfo), | ||||
|               key: args.key, album: args.album), | ||||
|           transitionsBuilder: TransitionsBuilders.slideBottom, | ||||
|           opaque: true, | ||||
|           barrierDismissible: false); | ||||
| @@ -447,27 +447,26 @@ class AlbumViewerRouteArgs { | ||||
| /// [SelectAdditionalUserForSharingPage] | ||||
| class SelectAdditionalUserForSharingRoute | ||||
|     extends PageRouteInfo<SelectAdditionalUserForSharingRouteArgs> { | ||||
|   SelectAdditionalUserForSharingRoute( | ||||
|       {Key? key, required AlbumResponseDto albumInfo}) | ||||
|   SelectAdditionalUserForSharingRoute({Key? key, required Album album}) | ||||
|       : super(SelectAdditionalUserForSharingRoute.name, | ||||
|             path: '/select-additional-user-for-sharing-page', | ||||
|             args: SelectAdditionalUserForSharingRouteArgs( | ||||
|                 key: key, albumInfo: albumInfo)); | ||||
|                 key: key, album: album)); | ||||
|  | ||||
|   static const String name = 'SelectAdditionalUserForSharingRoute'; | ||||
| } | ||||
|  | ||||
| class SelectAdditionalUserForSharingRouteArgs { | ||||
|   const SelectAdditionalUserForSharingRouteArgs( | ||||
|       {this.key, required this.albumInfo}); | ||||
|       {this.key, required this.album}); | ||||
|  | ||||
|   final Key? key; | ||||
|  | ||||
|   final AlbumResponseDto albumInfo; | ||||
|   final Album album; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'SelectAdditionalUserForSharingRouteArgs{key: $key, albumInfo: $albumInfo}'; | ||||
|     return 'SelectAdditionalUserForSharingRouteArgs{key: $key, album: $album}'; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										132
									
								
								mobile/lib/shared/models/album.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								mobile/lib/shared/models/album.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class Album { | ||||
|   Album.remote(AlbumResponseDto dto) | ||||
|       : remoteId = dto.id, | ||||
|         name = dto.albumName, | ||||
|         createdAt = DateTime.parse(dto.createdAt), | ||||
|         // TODO add modifiedAt to server | ||||
|         modifiedAt = DateTime.parse(dto.createdAt), | ||||
|         shared = dto.shared, | ||||
|         ownerId = dto.ownerId, | ||||
|         albumThumbnailAssetId = dto.albumThumbnailAssetId, | ||||
|         assetCount = dto.assetCount, | ||||
|         sharedUsers = dto.sharedUsers.map((e) => User.fromDto(e)).toList(), | ||||
|         assets = dto.assets.map(Asset.remote).toList(); | ||||
|  | ||||
|   Album({ | ||||
|     this.remoteId, | ||||
|     this.localId, | ||||
|     required this.name, | ||||
|     required this.ownerId, | ||||
|     required this.createdAt, | ||||
|     required this.modifiedAt, | ||||
|     required this.shared, | ||||
|     required this.assetCount, | ||||
|     this.albumThumbnailAssetId, | ||||
|     this.sharedUsers = const [], | ||||
|     this.assets = const [], | ||||
|   }); | ||||
|  | ||||
|   String? remoteId; | ||||
|   String? localId; | ||||
|   String name; | ||||
|   String ownerId; | ||||
|   DateTime createdAt; | ||||
|   DateTime modifiedAt; | ||||
|   bool shared; | ||||
|   String? albumThumbnailAssetId; | ||||
|   int assetCount; | ||||
|   List<User> sharedUsers = const []; | ||||
|   List<Asset> assets = const []; | ||||
|  | ||||
|   bool get isRemote => remoteId != null; | ||||
|  | ||||
|   bool get isLocal => localId != null; | ||||
|  | ||||
|   String get id => isRemote ? remoteId! : localId!; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(other) { | ||||
|     if (other is! Album) return false; | ||||
|     return remoteId == other.remoteId && | ||||
|         localId == other.localId && | ||||
|         name == other.name && | ||||
|         createdAt == other.createdAt && | ||||
|         modifiedAt == other.modifiedAt && | ||||
|         shared == other.shared && | ||||
|         ownerId == other.ownerId && | ||||
|         albumThumbnailAssetId == other.albumThumbnailAssetId; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|       remoteId.hashCode ^ | ||||
|       localId.hashCode ^ | ||||
|       name.hashCode ^ | ||||
|       createdAt.hashCode ^ | ||||
|       modifiedAt.hashCode ^ | ||||
|       shared.hashCode ^ | ||||
|       ownerId.hashCode ^ | ||||
|       albumThumbnailAssetId.hashCode; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|     json["remoteId"] = remoteId; | ||||
|     json["localId"] = localId; | ||||
|     json["name"] = name; | ||||
|     json["ownerId"] = ownerId; | ||||
|     json["createdAt"] = createdAt.millisecondsSinceEpoch; | ||||
|     json["modifiedAt"] = modifiedAt.millisecondsSinceEpoch; | ||||
|     json["shared"] = shared; | ||||
|     json["albumThumbnailAssetId"] = albumThumbnailAssetId; | ||||
|     json["assetCount"] = assetCount; | ||||
|     json["sharedUsers"] = sharedUsers; | ||||
|     json["assets"] = assets; | ||||
|     return json; | ||||
|   } | ||||
|  | ||||
|   static Album? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
|       return Album( | ||||
|         remoteId: json["remoteId"], | ||||
|         localId: json["localId"], | ||||
|         name: json["name"], | ||||
|         ownerId: json["ownerId"], | ||||
|         createdAt: DateTime.fromMillisecondsSinceEpoch( | ||||
|           json["createdAt"], | ||||
|           isUtc: true, | ||||
|         ), | ||||
|         modifiedAt: DateTime.fromMillisecondsSinceEpoch( | ||||
|           json["modifiedAt"], | ||||
|           isUtc: true, | ||||
|         ), | ||||
|         shared: json["shared"], | ||||
|         albumThumbnailAssetId: json["albumThumbnailAssetId"], | ||||
|         assetCount: json["assetCount"], | ||||
|         sharedUsers: _listFromJson<User>(json["sharedUsers"], User.fromJson), | ||||
|         assets: _listFromJson<Asset>(json["assets"], Asset.fromJson), | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
|  | ||||
| List<T> _listFromJson<T>( | ||||
|   dynamic json, | ||||
|   T? Function(dynamic) fromJson, | ||||
| ) { | ||||
|   final result = <T>[]; | ||||
|   if (json is List && json.isNotEmpty) { | ||||
|     for (final entry in json) { | ||||
|       final value = fromJson(entry); | ||||
|       if (value != null) { | ||||
|         result.add(value); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
							
								
								
									
										94
									
								
								mobile/lib/shared/models/user.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								mobile/lib/shared/models/user.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class User { | ||||
|   User({ | ||||
|     required this.id, | ||||
|     required this.email, | ||||
|     required this.firstName, | ||||
|     required this.lastName, | ||||
|     required this.profileImagePath, | ||||
|     required this.isAdmin, | ||||
|     required this.oauthId, | ||||
|   }); | ||||
|  | ||||
|   User.fromDto(UserResponseDto dto) | ||||
|       : id = dto.id, | ||||
|         email = dto.email, | ||||
|         firstName = dto.firstName, | ||||
|         lastName = dto.lastName, | ||||
|         profileImagePath = dto.profileImagePath, | ||||
|         isAdmin = dto.isAdmin, | ||||
|         oauthId = dto.oauthId; | ||||
|  | ||||
|   String id; | ||||
|   String email; | ||||
|   String firstName; | ||||
|   String lastName; | ||||
|   String profileImagePath; | ||||
|   bool isAdmin; | ||||
|   String oauthId; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(other) { | ||||
|     if (other is! User) return false; | ||||
|     return id == other.id && | ||||
|         email == other.email && | ||||
|         firstName == other.firstName && | ||||
|         lastName == other.lastName && | ||||
|         profileImagePath == other.profileImagePath && | ||||
|         isAdmin == other.isAdmin && | ||||
|         oauthId == other.oauthId; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|       id.hashCode ^ | ||||
|       email.hashCode ^ | ||||
|       firstName.hashCode ^ | ||||
|       lastName.hashCode ^ | ||||
|       profileImagePath.hashCode ^ | ||||
|       isAdmin.hashCode ^ | ||||
|       oauthId.hashCode; | ||||
|  | ||||
|   UserResponseDto toDto() { | ||||
|     return UserResponseDto( | ||||
|       id: id, | ||||
|       email: email, | ||||
|       firstName: firstName, | ||||
|       lastName: lastName, | ||||
|       profileImagePath: profileImagePath, | ||||
|       createdAt: '', | ||||
|       isAdmin: isAdmin, | ||||
|       shouldChangePassword: false, | ||||
|       oauthId: oauthId, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|     json["id"] = id; | ||||
|     json["email"] = email; | ||||
|     json["firstName"] = firstName; | ||||
|     json["lastName"] = lastName; | ||||
|     json["profileImagePath"] = profileImagePath; | ||||
|     json["isAdmin"] = isAdmin; | ||||
|     json["oauthId"] = oauthId; | ||||
|     return json; | ||||
|   } | ||||
|  | ||||
|   static User? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
|       return User( | ||||
|         id: json["id"], | ||||
|         email: json["email"], | ||||
|         firstName: json["firstName"], | ||||
|         lastName: json["lastName"], | ||||
|         profileImagePath: json["profileImagePath"], | ||||
|         isAdmin: json["isAdmin"], | ||||
|         oauthId: json["oauthId"], | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
| @@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:http_parser/http_parser.dart'; | ||||
| import 'package:image_picker/image_picker.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
| import 'package:immich_mobile/shared/providers/api.provider.dart'; | ||||
| import 'package:immich_mobile/shared/services/api.service.dart'; | ||||
| import 'package:immich_mobile/utils/files_helper.dart'; | ||||
| @@ -19,9 +20,10 @@ class UserService { | ||||
|  | ||||
|   UserService(this._apiService); | ||||
|  | ||||
|   Future<List<UserResponseDto>?> getAllUsersInfo({required bool isAll}) async { | ||||
|   Future<List<User>?> getAllUsers({required bool isAll}) async { | ||||
|     try { | ||||
|       return await _apiService.userApi.getAllUsers(isAll); | ||||
|       final dto = await _apiService.userApi.getAllUsers(isAll); | ||||
|       return dto?.map(User.fromDto).toList(); | ||||
|     } catch (e) { | ||||
|       debugPrint("Error [getAllUsersInfo]  ${e.toString()}"); | ||||
|       return null; | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import 'package:hive/hive.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| @@ -15,7 +16,7 @@ String getThumbnailCacheKey( | ||||
|   final Asset asset, { | ||||
|   ThumbnailFormat type = ThumbnailFormat.WEBP, | ||||
| }) { | ||||
|   return _getThumbnailCacheKey(asset.id, type); | ||||
|   return _getThumbnailCacheKey(asset.remoteId!, type); | ||||
| } | ||||
|  | ||||
| String _getThumbnailCacheKey(final String id, final ThumbnailFormat type) { | ||||
| @@ -27,7 +28,7 @@ String _getThumbnailCacheKey(final String id, final ThumbnailFormat type) { | ||||
| } | ||||
|  | ||||
| String getAlbumThumbnailUrl( | ||||
|   final AlbumResponseDto album, { | ||||
|   final Album album, { | ||||
|   ThumbnailFormat type = ThumbnailFormat.WEBP, | ||||
| }) { | ||||
|   if (album.albumThumbnailAssetId == null) { | ||||
| @@ -37,7 +38,7 @@ String getAlbumThumbnailUrl( | ||||
| } | ||||
|  | ||||
| String getAlbumThumbNailCacheKey( | ||||
|   final AlbumResponseDto album, { | ||||
|   final Album album, { | ||||
|   ThumbnailFormat type = ThumbnailFormat.WEBP, | ||||
| }) { | ||||
|   if (album.albumThumbnailAssetId == null) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user