From 2139853dd90523270263d95f51b61a2ac88530a0 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey Date: Mon, 6 Feb 2023 08:13:32 +0100 Subject: [PATCH] refactor(mobile): introduce Album & User classes (#1561) replace usages of AlbumResponseDto with Album replace usages of UserResponseDto with User --- .../album/providers/album.provider.dart | 28 ++-- .../providers/album_viewer.provider.dart | 7 +- .../providers/shared_album.provider.dart | 47 ++++--- .../suggested_shared_users.provider.dart | 6 +- .../modules/album/services/album.service.dart | 68 +++++---- .../album/services/album_cache.service.dart | 23 ++- .../album/ui/add_to_album_bottom_sheet.dart | 10 +- .../album/ui/add_to_album_sliverlist.dart | 57 ++++---- .../album/ui/album_thumbnail_card.dart | 5 +- .../album/ui/album_thumbnail_listtile.dart | 5 +- .../modules/album/ui/album_viewer_appbar.dart | 43 +++--- .../album/ui/album_viewer_editable_title.dart | 13 +- .../album/views/album_viewer_page.dart | 103 +++++++------- .../lib/modules/album/views/library_page.dart | 6 +- ...lect_additional_user_for_sharing_page.dart | 19 +-- .../views/select_user_for_sharing_page.dart | 12 +- .../lib/modules/album/views/sharing_page.dart | 6 +- .../home/ui/control_bottom_app_bar.dart | 8 +- mobile/lib/modules/home/views/home_page.dart | 10 +- mobile/lib/routing/router.dart | 2 +- mobile/lib/routing/router.gr.dart | 13 +- mobile/lib/shared/models/album.dart | 132 ++++++++++++++++++ mobile/lib/shared/models/user.dart | 94 +++++++++++++ mobile/lib/shared/services/user.service.dart | 6 +- mobile/lib/utils/image_url_builder.dart | 7 +- 25 files changed, 475 insertions(+), 255 deletions(-) create mode 100644 mobile/lib/shared/models/album.dart create mode 100644 mobile/lib/shared/models/user.dart diff --git a/mobile/lib/modules/album/providers/album.provider.dart b/mobile/lib/modules/album/providers/album.provider.dart index 5d7911fbd8..4c695faa40 100644 --- a/mobile/lib/modules/album/providers/album.provider.dart +++ b/mobile/lib/modules/album/providers/album.provider.dart @@ -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> { +class AlbumNotifier extends StateNotifier> { AlbumNotifier(this._albumService, this._albumCacheService) : super([]); final AlbumService _albumService; final AlbumCacheService _albumCacheService; - _cacheState() { + void _cacheState() { _albumCacheService.put(state); } - getAllAlbums() async { + Future getAllAlbums() async { if (await _albumCacheService.isValid() && state.isEmpty) { - state = await _albumCacheService.get(); + final albums = await _albumCacheService.get(); + if (albums != null) { + state = albums; + } } - List? 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> { } } - 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 createAlbum( + Future createAlbum( String albumTitle, Set 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> { } } -final albumProvider = - StateNotifierProvider>((ref) { +final albumProvider = StateNotifierProvider>((ref) { return AlbumNotifier( ref.watch(albumServiceProvider), ref.watch(albumCacheServiceProvider), diff --git a/mobile/lib/modules/album/providers/album_viewer.provider.dart b/mobile/lib/modules/album/providers/album_viewer.provider.dart index 371d6c49c4..b427e0248e 100644 --- a/mobile/lib/modules/album/providers/album_viewer.provider.dart +++ b/mobile/lib/modules/album/providers/album_viewer.provider.dart @@ -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 { AlbumViewerNotifier(this.ref) @@ -30,14 +31,12 @@ class AlbumViewerNotifier extends StateNotifier { } Future 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); diff --git a/mobile/lib/modules/album/providers/shared_album.provider.dart b/mobile/lib/modules/album/providers/shared_album.provider.dart index eefc09163e..ab950955bc 100644 --- a/mobile/lib/modules/album/providers/shared_album.provider.dart +++ b/mobile/lib/modules/album/providers/shared_album.provider.dart @@ -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> { +class SharedAlbumNotifier extends StateNotifier> { SharedAlbumNotifier(this._albumService, this._sharedAlbumCacheService) : super([]); final AlbumService _albumService; final SharedAlbumCacheService _sharedAlbumCacheService; - _cacheState() { + void _cacheState() { _sharedAlbumCacheService.put(state); } - Future createSharedAlbum( + Future createSharedAlbum( String albumName, - Set assets, - List sharedUserIds, + Iterable assets, + Iterable sharedUsers, ) async { try { var newAlbum = await _albumService.createAlbum( albumName, assets, - sharedUserIds, + sharedUsers, ); if (newAlbum != null) { @@ -41,13 +42,15 @@ class SharedAlbumNotifier extends StateNotifier> { } } - getAllSharedAlbums() async { + Future getAllSharedAlbums() async { if (await _sharedAlbumCacheService.isValid() && state.isEmpty) { - state = await _sharedAlbumCacheService.get(); + final albums = await _sharedAlbumCacheService.get(); + if (albums != null) { + state = albums; + } } - List? sharedAlbums = - await _albumService.getAlbums(isShared: true); + List? sharedAlbums = await _albumService.getAlbums(isShared: true); if (sharedAlbums != null) { state = sharedAlbums; @@ -55,16 +58,16 @@ class SharedAlbumNotifier extends StateNotifier> { } } - 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 leaveAlbum(String albumId) async { - var res = await _albumService.leaveAlbum(albumId); + Future 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> { } Future removeAssetFromAlbum( - String albumId, - List assetIds, + Album album, + Iterable 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> { } final sharedAlbumProvider = - StateNotifierProvider>((ref) { + StateNotifierProvider>((ref) { return SharedAlbumNotifier( ref.watch(albumServiceProvider), ref.watch(sharedAlbumCacheServiceProvider), ); }); -final sharedAlbumDetailProvider = FutureProvider.autoDispose - .family((ref, albumId) async { +final sharedAlbumDetailProvider = + FutureProvider.autoDispose.family((ref, albumId) async { final AlbumService sharedAlbumService = ref.watch(albumServiceProvider); return await sharedAlbumService.getAlbumDetail(albumId); diff --git a/mobile/lib/modules/album/providers/suggested_shared_users.provider.dart b/mobile/lib/modules/album/providers/suggested_shared_users.provider.dart index 0c4a2a3503..487bbcda80 100644 --- a/mobile/lib/modules/album/providers/suggested_shared_users.provider.dart +++ b/mobile/lib/modules/album/providers/suggested_shared_users.provider.dart @@ -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>((ref) async { + FutureProvider.autoDispose>((ref) async { UserService userService = ref.watch(userServiceProvider); - return await userService.getAllUsersInfo(isAll: false) ?? []; + return await userService.getAllUsers(isAll: false) ?? []; }); diff --git a/mobile/lib/modules/album/services/album.service.dart b/mobile/lib/modules/album/services/album.service.dart index fd82cd06fc..9e4cf8ee90 100644 --- a/mobile/lib/modules/album/services/album.service.dart +++ b/mobile/lib/modules/album/services/album.service.dart @@ -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?> getAlbums({required bool isShared}) async { + Future?> 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 createAlbum( + Future createAlbum( String albumName, - Iterable assets, - List sharedUserIds, - ) async { + Iterable assets, [ + Iterable 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? albums) { + String _getNextAlbumName(List? 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 createAlbumWithGeneratedName( + Future createAlbumWithGeneratedName( Iterable assets, ) async { return createAlbum( @@ -75,9 +79,10 @@ class AlbumService { ); } - Future getAlbumDetail(String albumId) async { + Future 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 addAdditionalAssetToAlbum( Iterable 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 addAdditionalUserToAlbum( List 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 deleteAlbum(String albumId) async { + Future 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 leaveAlbum(String albumId) async { + Future 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 removeAssetFromAlbum( - String albumId, - List assetIds, + Album album, + Iterable 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 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) { diff --git a/mobile/lib/modules/album/services/album_cache.service.dart b/mobile/lib/modules/album/services/album_cache.service.dart index 0e16056585..c665d1420b 100644 --- a/mobile/lib/modules/album/services/album_cache.service.dart +++ b/mobile/lib/modules/album/services/album_cache.service.dart @@ -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> { +class BaseAlbumCacheService extends JsonCache> { BaseAlbumCacheService(super.cacheFileName); @override - void put(List data) { + void put(List data) { putRawData(data.map((e) => e.toJson()).toList()); } @override - Future> get() async { + Future?> get() async { try { final mapList = await readRawData() as List; - 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(), ); - diff --git a/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart b/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart index 8b919c479d..0273c01265 100644 --- a/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart +++ b/mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart @@ -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}', ); } } diff --git a/mobile/lib/modules/album/ui/add_to_album_sliverlist.dart b/mobile/lib/modules/album/ui/add_to_album_sliverlist.dart index fd1be2374f..82bd21809f 100644 --- a/mobile/lib/modules/album/ui/add_to_album_sliverlist.dart +++ b/mobile/lib/modules/album/ui/add_to_album_sliverlist.dart @@ -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 albums; - final List sharedAlbums; - final void Function(AlbumResponseDto) onAddToAlbum; + final List albums; + final List 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), + ); + }), ); } } diff --git a/mobile/lib/modules/album/ui/album_thumbnail_card.dart b/mobile/lib/modules/album/ui/album_thumbnail_card.dart index f2505f4133..4887e7d05c 100644 --- a/mobile/lib/modules/album/ui/album_thumbnail_card.dart +++ b/mobile/lib/modules/album/ui/album_thumbnail_card.dart @@ -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, ), diff --git a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart index 2801ec00df..d00609ad60 100644 --- a/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart +++ b/mobile/lib/modules/album/ui/album_thumbnail_listtile.dart @@ -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, ), diff --git a/mobile/lib/modules/album/ui/album_viewer_appbar.dart b/mobile/lib/modules/album/ui/album_viewer_appbar.dart index 00a66a18ee..71e36eff78 100644 --- a/mobile/lib/modules/album/ui/album_viewer_appbar.dart +++ b/mobile/lib/modules/album/ui/album_viewer_appbar.dart @@ -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( diff --git a/mobile/lib/modules/album/ui/album_viewer_editable_title.dart b/mobile/lib/modules/album/ui/album_viewer_editable_title.dart index 9c9c137c84..b7a5d3544c 100644 --- a/mobile/lib/modules/album/ui/album_viewer_editable_title.dart +++ b/mobile/lib/modules/album/ui/album_viewer_editable_title.dart @@ -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') { diff --git a/mobile/lib/modules/album/views/album_viewer_page.dart b/mobile/lib/modules/album/views/album_viewer_page.dart index 7e2dfac83e..23675c1cbb 100644 --- a/mobile/lib/modules/album/views/album_viewer_page.dart +++ b/mobile/lib/modules/album/views/album_viewer_page.dart @@ -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? sharedUserIds = await AutoRouter.of(context).push?>( - 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 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( diff --git a/mobile/lib/modules/album/views/library_page.dart b/mobile/lib/modules/album/views/library_page.dart index 1dc8ab64ab..7c9475d3a4 100644 --- a/mobile/lib/modules/album/views/library_page.dart +++ b/mobile/lib/modules/album/views/library_page.dart @@ -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 sortedAlbums() { + List 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() { diff --git a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart index 1125e83caa..1aadeb3a6e 100644 --- a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart @@ -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> suggestedShareUsers = + final AsyncValue> suggestedShareUsers = ref.watch(suggestedSharedUsersProvider); - final sharedUsersList = useState>({}); + final sharedUsersList = useState>({}); 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 users) { + buildUserList(List users) { List 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, ); } diff --git a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart index cbccbb1b3d..976a781e29 100644 --- a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart @@ -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>({}); - AsyncValue> suggestedShareUsers = + final sharedUsersList = useState>({}); + AsyncValue> 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 users) { + buildUserList(List users) { List usersChip = []; for (var user in sharedUsersList.value) { diff --git a/mobile/lib/modules/album/views/sharing_page.dart b/mobile/lib/modules/album/views/sharing_page.dart index 2b84a3b857..9a69a2724a 100644 --- a/mobile/lib/modules/album/views/sharing_page.dart +++ b/mobile/lib/modules/album/views/sharing_page.dart @@ -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 sharedAlbums = ref.watch(sharedAlbumProvider); + final List 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( diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart index edc608637c..cf5359b2cc 100644 --- a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart +++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart @@ -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 albums; - final List sharedAlbums; + final List albums; + final List sharedAlbums; const ControlBottomAppBar({ Key? key, diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index d032a61651..f299d6e87c 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -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 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(), }, ), diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index ace3629288..698f97571a 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -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'; diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 0a1311460d..6ea3192a79 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -108,7 +108,7 @@ class _$AppRouter extends RootStackRouter { return CustomPage?>( 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 { - 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}'; } } diff --git a/mobile/lib/shared/models/album.dart b/mobile/lib/shared/models/album.dart new file mode 100644 index 0000000000..21d2079c50 --- /dev/null +++ b/mobile/lib/shared/models/album.dart @@ -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 sharedUsers = const []; + List 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 toJson() { + final json = {}; + 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(); + 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(json["sharedUsers"], User.fromJson), + assets: _listFromJson(json["assets"], Asset.fromJson), + ); + } + return null; + } +} + +List _listFromJson( + dynamic json, + T? Function(dynamic) fromJson, +) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final entry in json) { + final value = fromJson(entry); + if (value != null) { + result.add(value); + } + } + } + return result; +} diff --git a/mobile/lib/shared/models/user.dart b/mobile/lib/shared/models/user.dart new file mode 100644 index 0000000000..a248dd2479 --- /dev/null +++ b/mobile/lib/shared/models/user.dart @@ -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 toJson() { + final json = {}; + 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(); + 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; + } +} diff --git a/mobile/lib/shared/services/user.service.dart b/mobile/lib/shared/services/user.service.dart index 114293a5da..398ef2aceb 100644 --- a/mobile/lib/shared/services/user.service.dart +++ b/mobile/lib/shared/services/user.service.dart @@ -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?> getAllUsersInfo({required bool isAll}) async { + Future?> 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; diff --git a/mobile/lib/utils/image_url_builder.dart b/mobile/lib/utils/image_url_builder.dart index 3ffe4b547a..f5591dd923 100644 --- a/mobile/lib/utils/image_url_builder.dart +++ b/mobile/lib/utils/image_url_builder.dart @@ -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) {