1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02: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:
Fynn Petersen-Frey 2023-02-06 08:13:32 +01:00 committed by GitHub
parent 527aa61a87
commit 2139853dd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 475 additions and 255 deletions

View File

@ -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.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:immich_mobile/shared/models/asset.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([]); AlbumNotifier(this._albumService, this._albumCacheService) : super([]);
final AlbumService _albumService; final AlbumService _albumService;
final AlbumCacheService _albumCacheService; final AlbumCacheService _albumCacheService;
_cacheState() { void _cacheState() {
_albumCacheService.put(state); _albumCacheService.put(state);
} }
getAllAlbums() async { Future<void> getAllAlbums() async {
if (await _albumCacheService.isValid() && state.isEmpty) { if (await _albumCacheService.isValid() && state.isEmpty) {
state = await _albumCacheService.get(); final albums = await _albumCacheService.get();
if (albums != null) {
state = albums;
}
} }
List<AlbumResponseDto>? albums = final albums = await _albumService.getAlbums(isShared: false);
await _albumService.getAlbums(isShared: false);
if (albums != null) { if (albums != null) {
state = albums; state = albums;
@ -27,17 +29,16 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
} }
} }
deleteAlbum(String albumId) { void deleteAlbum(Album album) {
state = state.where((album) => album.id != albumId).toList(); state = state.where((a) => a.id != album.id).toList();
_cacheState(); _cacheState();
} }
Future<AlbumResponseDto?> createAlbum( Future<Album?> createAlbum(
String albumTitle, String albumTitle,
Set<Asset> assets, Set<Asset> assets,
) async { ) async {
AlbumResponseDto? album = Album? album = await _albumService.createAlbum(albumTitle, assets, []);
await _albumService.createAlbum(albumTitle, assets, []);
if (album != null) { if (album != null) {
state = [...state, album]; state = [...state, album];
@ -49,8 +50,7 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
} }
} }
final albumProvider = final albumProvider = StateNotifierProvider<AlbumNotifier, List<Album>>((ref) {
StateNotifierProvider<AlbumNotifier, List<AlbumResponseDto>>((ref) {
return AlbumNotifier( return AlbumNotifier(
ref.watch(albumServiceProvider), ref.watch(albumServiceProvider),
ref.watch(albumCacheServiceProvider), ref.watch(albumCacheServiceProvider),

View File

@ -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/models/album_viewer_page_state.model.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.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/modules/album/services/album.service.dart';
import 'package:immich_mobile/shared/models/album.dart';
class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> { class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
AlbumViewerNotifier(this.ref) AlbumViewerNotifier(this.ref)
@ -30,14 +31,12 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
} }
Future<bool> changeAlbumTitle( Future<bool> changeAlbumTitle(
String albumId, Album album,
String ownerId,
String newAlbumTitle, String newAlbumTitle,
) async { ) async {
AlbumService service = ref.watch(albumServiceProvider); AlbumService service = ref.watch(albumServiceProvider);
bool isSuccess = bool isSuccess = await service.changeTitleAlbum(album, newAlbumTitle);
await service.changeTitleAlbum(albumId, ownerId, newAlbumTitle);
if (isSuccess) { if (isSuccess) {
state = state.copyWith(editTitleText: "", isEditAlbum: false); state = state.copyWith(editTitleText: "", isEditAlbum: false);

View File

@ -2,30 +2,31 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.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: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) SharedAlbumNotifier(this._albumService, this._sharedAlbumCacheService)
: super([]); : super([]);
final AlbumService _albumService; final AlbumService _albumService;
final SharedAlbumCacheService _sharedAlbumCacheService; final SharedAlbumCacheService _sharedAlbumCacheService;
_cacheState() { void _cacheState() {
_sharedAlbumCacheService.put(state); _sharedAlbumCacheService.put(state);
} }
Future<AlbumResponseDto?> createSharedAlbum( Future<Album?> createSharedAlbum(
String albumName, String albumName,
Set<Asset> assets, Iterable<Asset> assets,
List<String> sharedUserIds, Iterable<User> sharedUsers,
) async { ) async {
try { try {
var newAlbum = await _albumService.createAlbum( var newAlbum = await _albumService.createAlbum(
albumName, albumName,
assets, assets,
sharedUserIds, sharedUsers,
); );
if (newAlbum != null) { 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) { if (await _sharedAlbumCacheService.isValid() && state.isEmpty) {
state = await _sharedAlbumCacheService.get(); final albums = await _sharedAlbumCacheService.get();
if (albums != null) {
state = albums;
}
} }
List<AlbumResponseDto>? sharedAlbums = List<Album>? sharedAlbums = await _albumService.getAlbums(isShared: true);
await _albumService.getAlbums(isShared: true);
if (sharedAlbums != null) { if (sharedAlbums != null) {
state = sharedAlbums; state = sharedAlbums;
@ -55,16 +58,16 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
} }
} }
deleteAlbum(String albumId) async { void deleteAlbum(Album album) {
state = state.where((album) => album.id != albumId).toList(); state = state.where((a) => a.id != album.id).toList();
_cacheState(); _cacheState();
} }
Future<bool> leaveAlbum(String albumId) async { Future<bool> leaveAlbum(Album album) async {
var res = await _albumService.leaveAlbum(albumId); var res = await _albumService.leaveAlbum(album);
if (res) { if (res) {
state = state.where((album) => album.id != albumId).toList(); state = state.where((a) => a.id != album.id).toList();
_cacheState(); _cacheState();
return true; return true;
} else { } else {
@ -73,10 +76,10 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
} }
Future<bool> removeAssetFromAlbum( Future<bool> removeAssetFromAlbum(
String albumId, Album album,
List<String> assetIds, Iterable<Asset> assets,
) async { ) async {
var res = await _albumService.removeAssetFromAlbum(albumId, assetIds); var res = await _albumService.removeAssetFromAlbum(album, assets);
if (res) { if (res) {
return true; return true;
@ -87,15 +90,15 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
} }
final sharedAlbumProvider = final sharedAlbumProvider =
StateNotifierProvider<SharedAlbumNotifier, List<AlbumResponseDto>>((ref) { StateNotifierProvider<SharedAlbumNotifier, List<Album>>((ref) {
return SharedAlbumNotifier( return SharedAlbumNotifier(
ref.watch(albumServiceProvider), ref.watch(albumServiceProvider),
ref.watch(sharedAlbumCacheServiceProvider), ref.watch(sharedAlbumCacheServiceProvider),
); );
}); });
final sharedAlbumDetailProvider = FutureProvider.autoDispose final sharedAlbumDetailProvider =
.family<AlbumResponseDto?, String>((ref, albumId) async { FutureProvider.autoDispose.family<Album?, String>((ref, albumId) async {
final AlbumService sharedAlbumService = ref.watch(albumServiceProvider); final AlbumService sharedAlbumService = ref.watch(albumServiceProvider);
return await sharedAlbumService.getAlbumDetail(albumId); return await sharedAlbumService.getAlbumDetail(albumId);

View File

@ -1,10 +1,10 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; 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:immich_mobile/shared/services/user.service.dart';
import 'package:openapi/api.dart';
final suggestedSharedUsersProvider = final suggestedSharedUsersProvider =
FutureProvider.autoDispose<List<UserResponseDto>>((ref) async { FutureProvider.autoDispose<List<User>>((ref) async {
UserService userService = ref.watch(userServiceProvider); UserService userService = ref.watch(userServiceProvider);
return await userService.getAllUsersInfo(isAll: false) ?? []; return await userService.getAllUsers(isAll: false) ?? [];
}); });

View File

@ -2,7 +2,9 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/asset.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart'; import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart'; import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@ -18,29 +20,31 @@ class AlbumService {
AlbumService(this._apiService); AlbumService(this._apiService);
Future<List<AlbumResponseDto>?> getAlbums({required bool isShared}) async { Future<List<Album>?> getAlbums({required bool isShared}) async {
try { try {
return await _apiService.albumApi final dto = await _apiService.albumApi
.getAllAlbums(shared: isShared ? isShared : null); .getAllAlbums(shared: isShared ? isShared : null);
return dto?.map(Album.remote).toList();
} catch (e) { } catch (e) {
debugPrint("Error getAllSharedAlbum ${e.toString()}"); debugPrint("Error getAllSharedAlbum ${e.toString()}");
return null; return null;
} }
} }
Future<AlbumResponseDto?> createAlbum( Future<Album?> createAlbum(
String albumName, String albumName,
Iterable<Asset> assets, Iterable<Asset> assets, [
List<String> sharedUserIds, Iterable<User> sharedUsers = const [],
) async { ]) async {
try { try {
return await _apiService.albumApi.createAlbum( final dto = await _apiService.albumApi.createAlbum(
CreateAlbumDto( CreateAlbumDto(
albumName: albumName, albumName: albumName,
assetIds: assets.map((asset) => asset.id).toList(), assetIds: assets.map((asset) => asset.remoteId!).toList(),
sharedWithUserIds: sharedUserIds, sharedWithUserIds: sharedUsers.map((e) => e.id).toList(),
), ),
); );
return dto != null ? Album.remote(dto) : null;
} catch (e) { } catch (e) {
debugPrint("Error createSharedAlbum ${e.toString()}"); debugPrint("Error createSharedAlbum ${e.toString()}");
return null; return null;
@ -50,14 +54,14 @@ class AlbumService {
/* /*
* Creates names like Untitled, Untitled (1), Untitled (2), ... * Creates names like Untitled, Untitled (1), Untitled (2), ...
*/ */
String _getNextAlbumName(List<AlbumResponseDto>? albums) { String _getNextAlbumName(List<Album>? albums) {
const baseName = "Untitled"; const baseName = "Untitled";
if (albums != null) { if (albums != null) {
for (int round = 0; round < albums.length; round++) { for (int round = 0; round < albums.length; round++) {
final proposedName = "$baseName${round == 0 ? "" : " ($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; return proposedName;
} }
} }
@ -65,7 +69,7 @@ class AlbumService {
return baseName; return baseName;
} }
Future<AlbumResponseDto?> createAlbumWithGeneratedName( Future<Album?> createAlbumWithGeneratedName(
Iterable<Asset> assets, Iterable<Asset> assets,
) async { ) async {
return createAlbum( return createAlbum(
@ -75,9 +79,10 @@ class AlbumService {
); );
} }
Future<AlbumResponseDto?> getAlbumDetail(String albumId) async { Future<Album?> getAlbumDetail(String albumId) async {
try { try {
return await _apiService.albumApi.getAlbumInfo(albumId); final dto = await _apiService.albumApi.getAlbumInfo(albumId);
return dto != null ? Album.remote(dto) : null;
} catch (e) { } catch (e) {
debugPrint('Error [getAlbumDetail] ${e.toString()}'); debugPrint('Error [getAlbumDetail] ${e.toString()}');
return null; return null;
@ -86,12 +91,12 @@ class AlbumService {
Future<AddAssetsResponseDto?> addAdditionalAssetToAlbum( Future<AddAssetsResponseDto?> addAdditionalAssetToAlbum(
Iterable<Asset> assets, Iterable<Asset> assets,
String albumId, Album album,
) async { ) async {
try { try {
var result = await _apiService.albumApi.addAssetsToAlbum( var result = await _apiService.albumApi.addAssetsToAlbum(
albumId, album.remoteId!,
AddAssetsDto(assetIds: assets.map((asset) => asset.id).toList()), AddAssetsDto(assetIds: assets.map((asset) => asset.remoteId!).toList()),
); );
return result; return result;
} catch (e) { } catch (e) {
@ -102,11 +107,11 @@ class AlbumService {
Future<bool> addAdditionalUserToAlbum( Future<bool> addAdditionalUserToAlbum(
List<String> sharedUserIds, List<String> sharedUserIds,
String albumId, Album album,
) async { ) async {
try { try {
var result = await _apiService.albumApi.addUsersToAlbum( var result = await _apiService.albumApi.addUsersToAlbum(
albumId, album.remoteId!,
AddUsersDto(sharedUserIds: sharedUserIds), AddUsersDto(sharedUserIds: sharedUserIds),
); );
@ -117,9 +122,9 @@ class AlbumService {
} }
} }
Future<bool> deleteAlbum(String albumId) async { Future<bool> deleteAlbum(Album album) async {
try { try {
await _apiService.albumApi.deleteAlbum(albumId); await _apiService.albumApi.deleteAlbum(album.remoteId!);
return true; return true;
} catch (e) { } catch (e) {
debugPrint("Error deleteAlbum ${e.toString()}"); debugPrint("Error deleteAlbum ${e.toString()}");
@ -127,10 +132,9 @@ class AlbumService {
} }
} }
Future<bool> leaveAlbum(String albumId) async { Future<bool> leaveAlbum(Album album) async {
try { try {
await _apiService.albumApi.removeUserFromAlbum(albumId, "me"); await _apiService.albumApi.removeUserFromAlbum(album.remoteId!, "me");
return true; return true;
} catch (e) { } catch (e) {
debugPrint("Error deleteAlbum ${e.toString()}"); debugPrint("Error deleteAlbum ${e.toString()}");
@ -139,13 +143,15 @@ class AlbumService {
} }
Future<bool> removeAssetFromAlbum( Future<bool> removeAssetFromAlbum(
String albumId, Album album,
List<String> assetIds, Iterable<Asset> assets,
) async { ) async {
try { try {
await _apiService.albumApi.removeAssetFromAlbum( await _apiService.albumApi.removeAssetFromAlbum(
albumId, album.remoteId!,
RemoveAssetsDto(assetIds: assetIds), RemoveAssetsDto(
assetIds: assets.map((e) => e.remoteId!).toList(growable: false),
),
); );
return true; return true;
@ -156,17 +162,17 @@ class AlbumService {
} }
Future<bool> changeTitleAlbum( Future<bool> changeTitleAlbum(
String albumId, Album album,
String ownerId,
String newAlbumTitle, String newAlbumTitle,
) async { ) async {
try { try {
await _apiService.albumApi.updateAlbumInfo( await _apiService.albumApi.updateAlbumInfo(
albumId, album.remoteId!,
UpdateAlbumDto( UpdateAlbumDto(
albumName: newAlbumTitle, albumName: newAlbumTitle,
), ),
); );
album.name = newAlbumTitle;
return true; return true;
} catch (e) { } catch (e) {

View File

@ -1,32 +1,30 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.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: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); BaseAlbumCacheService(super.cacheFileName);
@override @override
void put(List<AlbumResponseDto> data) { void put(List<Album> data) {
putRawData(data.map((e) => e.toJson()).toList()); putRawData(data.map((e) => e.toJson()).toList());
} }
@override @override
Future<List<AlbumResponseDto>> get() async { Future<List<Album>?> get() async {
try { try {
final mapList = await readRawData() as List<dynamic>; final mapList = await readRawData() as List<dynamic>;
final responseData = mapList final responseData =
.map((e) => AlbumResponseDto.fromJson(e)) mapList.map((e) => Album.fromJson(e)).whereNotNull().toList();
.whereNotNull()
.toList();
return responseData; return responseData;
} catch (e) { } catch (e) {
await invalidate();
debugPrint(e.toString()); debugPrint(e.toString());
return []; return null;
} }
} }
} }
@ -40,10 +38,9 @@ class SharedAlbumCacheService extends BaseAlbumCacheService {
} }
final albumCacheServiceProvider = Provider( final albumCacheServiceProvider = Provider(
(ref) => AlbumCacheService(), (ref) => AlbumCacheService(),
); );
final sharedAlbumCacheServiceProvider = Provider( final sharedAlbumCacheServiceProvider = Provider(
(ref) => SharedAlbumCacheService(), (ref) => SharedAlbumCacheService(),
); );

View File

@ -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/services/album.service.dart';
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart'; import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
import 'package:immich_mobile/routing/router.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/models/asset.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.dart'; import 'package:immich_mobile/shared/ui/drag_sheet.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:openapi/api.dart';
class AddToAlbumBottomSheet extends HookConsumerWidget { class AddToAlbumBottomSheet extends HookConsumerWidget {
/// The asset to add to an album /// 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( final result = await albumService.addAdditionalAssetToAlbum(
assets, assets,
album.id, album,
); );
if (result != null) { if (result != null) {
if (result.alreadyInAlbum.isNotEmpty) { if (result.alreadyInAlbum.isNotEmpty) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: 'Already in ${album.albumName}', msg: 'Already in ${album.name}',
); );
} else { } else {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
msg: 'Added to ${album.albumName}', msg: 'Added to ${album.name}',
); );
} }
} }

View File

@ -1,14 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.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 { class AddToAlbumSliverList extends HookConsumerWidget {
/// The asset to add to an album /// The asset to add to an album
final List<AlbumResponseDto> albums; final List<Album> albums;
final List<AlbumResponseDto> sharedAlbums; final List<Album> sharedAlbums;
final void Function(AlbumResponseDto) onAddToAlbum; final void Function(Album) onAddToAlbum;
const AddToAlbumSliverList({ const AddToAlbumSliverList({
Key? key, Key? key,
@ -21,36 +20,36 @@ class AddToAlbumSliverList extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1), childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1),
(context, index) { (context, index) {
// Build shared expander // Build shared expander
if (index == 0 && sharedAlbums.isNotEmpty) { if (index == 0 && sharedAlbums.isNotEmpty) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: ExpansionTile( child: ExpansionTile(
title: const Text('Shared'), title: const Text('Shared'),
tilePadding: const EdgeInsets.symmetric(horizontal: 10.0), tilePadding: const EdgeInsets.symmetric(horizontal: 10.0),
leading: const Icon(Icons.group), leading: const Icon(Icons.group),
children: sharedAlbums.map((album) => children: sharedAlbums
AlbumThumbnailListTile( .map(
(album) => AlbumThumbnailListTile(
album: album, album: album,
onTap: () => onAddToAlbum(album), onTap: () => onAddToAlbum(album),
), ),
).toList(), )
), .toList(),
); ),
}
// Build albums list
final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0);
final album = albums[offset];
return AlbumThumbnailListTile(
album: album,
onTap: () => onAddToAlbum(album),
); );
} }
),
// Build albums list
final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0);
final album = albums[offset];
return AlbumThumbnailListTile(
album: album,
onTap: () => onAddToAlbum(album),
);
}),
); );
} }
} }

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/routing/router.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:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@ -14,7 +15,7 @@ class AlbumThumbnailCard extends StatelessWidget {
required this.album, required this.album,
}) : super(key: key); }) : super(key: key);
final AlbumResponseDto album; final Album album;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -72,7 +73,7 @@ class AlbumThumbnailCard extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: cardSize, width: cardSize,
child: Text( child: Text(
album.albumName, album.name,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:immich_mobile/constants/hive_box.dart'; import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/routing/router.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:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@ -15,7 +16,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
this.onTap, this.onTap,
}) : super(key: key); }) : super(key: key);
final AlbumResponseDto album; final Album album;
final void Function()? onTap; final void Function()? onTap;
@override @override
@ -80,7 +81,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
album.albumName, album.name,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),

View File

@ -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/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/routing/router.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/ui/immich_toast.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:openapi/api.dart';
class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
const AlbumViewerAppbar({ const AlbumViewerAppbar({
Key? key, Key? key,
required this.albumInfo, required this.album,
required this.userId, required this.userId,
required this.albumId,
}) : super(key: key); }) : super(key: key);
final AlbumResponseDto albumInfo; final Album album;
final String userId; final String userId;
final String albumId;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -34,19 +32,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText; final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum; final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
void onDeleteAlbumPressed(String albumId) async { void onDeleteAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
bool isSuccess = bool isSuccess = await ref.watch(albumServiceProvider).deleteAlbum(album);
await ref.watch(albumServiceProvider).deleteAlbum(albumId);
if (isSuccess) { if (isSuccess) {
if (albumInfo.shared) { if (album.shared) {
ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId); ref.watch(sharedAlbumProvider.notifier).deleteAlbum(album);
AutoRouter.of(context) AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()])); .navigate(const TabControllerRoute(children: [SharingRoute()]));
} else { } else {
ref.watch(albumProvider.notifier).deleteAlbum(albumId); ref.watch(albumProvider.notifier).deleteAlbum(album);
AutoRouter.of(context) AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [LibraryRoute()])); .navigate(const TabControllerRoute(children: [LibraryRoute()]));
} }
@ -62,11 +59,11 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
ImmichLoadingOverlayController.appLoader.hide(); ImmichLoadingOverlayController.appLoader.hide();
} }
void onLeaveAlbumPressed(String albumId) async { void onLeaveAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
bool isSuccess = bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId); await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(album);
if (isSuccess) { if (isSuccess) {
AutoRouter.of(context) AutoRouter.of(context)
@ -84,20 +81,20 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
ImmichLoadingOverlayController.appLoader.hide(); ImmichLoadingOverlayController.appLoader.hide();
} }
void onRemoveFromAlbumPressed(String albumId) async { void onRemoveFromAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show(); ImmichLoadingOverlayController.appLoader.show();
bool isSuccess = bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum( await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum(
albumId, album,
selectedAssetsInAlbum.map((a) => a.id).toList(), selectedAssetsInAlbum,
); );
if (isSuccess) { if (isSuccess) {
Navigator.pop(context); Navigator.pop(context);
ref.watch(assetSelectionProvider.notifier).disableMultiselection(); ref.watch(assetSelectionProvider.notifier).disableMultiselection();
ref.watch(albumProvider.notifier).getAllAlbums(); ref.watch(albumProvider.notifier).getAllAlbums();
ref.invalidate(sharedAlbumDetailProvider(albumId)); ref.invalidate(sharedAlbumDetailProvider(album.id));
} else { } else {
Navigator.pop(context); Navigator.pop(context);
ImmichToast.show( ImmichToast.show(
@ -113,27 +110,27 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
buildBottomSheetActionButton() { buildBottomSheetActionButton() {
if (isMultiSelectionEnable) { if (isMultiSelectionEnable) {
if (albumInfo.ownerId == userId) { if (album.ownerId == userId) {
return ListTile( return ListTile(
leading: const Icon(Icons.delete_sweep_rounded), leading: const Icon(Icons.delete_sweep_rounded),
title: const Text( title: const Text(
'album_viewer_appbar_share_remove', 'album_viewer_appbar_share_remove',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
).tr(), ).tr(),
onTap: () => onRemoveFromAlbumPressed(albumId), onTap: () => onRemoveFromAlbumPressed(),
); );
} else { } else {
return const SizedBox(); return const SizedBox();
} }
} else { } else {
if (albumInfo.ownerId == userId) { if (album.ownerId == userId) {
return ListTile( return ListTile(
leading: const Icon(Icons.delete_forever_rounded), leading: const Icon(Icons.delete_forever_rounded),
title: const Text( title: const Text(
'album_viewer_appbar_share_delete', 'album_viewer_appbar_share_delete',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
).tr(), ).tr(),
onTap: () => onDeleteAlbumPressed(albumId), onTap: () => onDeleteAlbumPressed(),
); );
} else { } else {
return ListTile( return ListTile(
@ -142,7 +139,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
'album_viewer_appbar_share_leave', 'album_viewer_appbar_share_leave',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
).tr(), ).tr(),
onTap: () => onLeaveAlbumPressed(albumId), onTap: () => onLeaveAlbumPressed(),
); );
} }
} }
@ -180,7 +177,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
onPressed: () async { onPressed: () async {
bool isSuccess = await ref bool isSuccess = await ref
.watch(albumViewerProvider.notifier) .watch(albumViewerProvider.notifier)
.changeAlbumTitle(albumId, userId, newAlbumTitle); .changeAlbumTitle(album, newAlbumTitle);
if (!isSuccess) { if (!isSuccess) {
ImmichToast.show( ImmichToast.show(

View File

@ -3,21 +3,20 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/album_viewer.provider.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 { class AlbumViewerEditableTitle extends HookConsumerWidget {
final AlbumResponseDto albumInfo; final Album album;
final FocusNode titleFocusNode; final FocusNode titleFocusNode;
const AlbumViewerEditableTitle({ const AlbumViewerEditableTitle({
Key? key, Key? key,
required this.albumInfo, required this.album,
required this.titleFocusNode, required this.titleFocusNode,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final titleTextEditController = final titleTextEditController = useTextEditingController(text: album.name);
useTextEditingController(text: albumInfo.albumName);
final isDarkTheme = Theme.of(context).brightness == Brightness.dark; final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
void onFocusModeChange() { void onFocusModeChange() {
@ -50,9 +49,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
onTap: () { onTap: () {
FocusScope.of(context).requestFocus(titleFocusNode); FocusScope.of(context).requestFocus(titleFocusNode);
ref ref.watch(albumViewerProvider.notifier).setEditTitleText(album.name);
.watch(albumViewerProvider.notifier)
.setEditTitleText(albumInfo.albumName);
ref.watch(albumViewerProvider.notifier).enableEditAlbum(); ref.watch(albumViewerProvider.notifier).enableEditAlbum();
if (titleTextEditController.text == 'Untitled') { if (titleTextEditController.text == 'Untitled') {

View File

@ -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/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/routing/router.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_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.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:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:openapi/api.dart';
class AlbumViewerPage extends HookConsumerWidget { class AlbumViewerPage extends HookConsumerWidget {
final String albumId; final String albumId;
@ -34,16 +33,16 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
FocusNode titleFocusNode = useFocusNode(); FocusNode titleFocusNode = useFocusNode();
ScrollController scrollController = useScrollController(); ScrollController scrollController = useScrollController();
var albumInfo = ref.watch(sharedAlbumDetailProvider(albumId)); final album = ref.watch(sharedAlbumDetailProvider(albumId));
final userId = ref.watch(authenticationProvider).userId; final userId = ref.watch(authenticationProvider).userId;
/// Find out if the assets in album exist on the device /// 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. /// 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) { if (albumInfo.assets.isNotEmpty == true) {
ref.watch(assetSelectionProvider.notifier).addNewAssets( 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 = var addAssetsResult =
await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum( await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum(
returnPayload.selectedAdditionalAsset, returnPayload.selectedAdditionalAsset,
albumId, albumInfo,
); );
if (addAssetsResult != null && if (addAssetsResult != null &&
@ -78,10 +77,10 @@ class AlbumViewerPage extends HookConsumerWidget {
} }
} }
void onAddUsersPressed(AlbumResponseDto albumInfo) async { void onAddUsersPressed(Album album) async {
List<String>? sharedUserIds = List<String>? sharedUserIds =
await AutoRouter.of(context).push<List<String>?>( await AutoRouter.of(context).push<List<String>?>(
SelectAdditionalUserForSharingRoute(albumInfo: albumInfo), SelectAdditionalUserForSharingRoute(album: album),
); );
if (sharedUserIds != null) { if (sharedUserIds != null) {
@ -89,7 +88,7 @@ class AlbumViewerPage extends HookConsumerWidget {
var isSuccess = await ref var isSuccess = await ref
.watch(albumServiceProvider) .watch(albumServiceProvider)
.addAdditionalUserToAlbum(sharedUserIds, albumId); .addAdditionalUserToAlbum(sharedUserIds, album);
if (isSuccess) { if (isSuccess) {
ref.invalidate(sharedAlbumDetailProvider(albumId)); ref.invalidate(sharedAlbumDetailProvider(albumId));
@ -99,18 +98,18 @@ class AlbumViewerPage extends HookConsumerWidget {
} }
} }
Widget buildTitle(AlbumResponseDto albumInfo) { Widget buildTitle(Album album) {
return Padding( return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 16), padding: const EdgeInsets.only(left: 8, right: 8, top: 16),
child: userId == albumInfo.ownerId child: userId == album.ownerId
? AlbumViewerEditableTitle( ? AlbumViewerEditableTitle(
albumInfo: albumInfo, album: album,
titleFocusNode: titleFocusNode, titleFocusNode: titleFocusNode,
) )
: Padding( : Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text( child: Text(
albumInfo.albumName, album.name,
style: const TextStyle( style: const TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -120,30 +119,22 @@ class AlbumViewerPage extends HookConsumerWidget {
); );
} }
Widget buildAlbumDateRange(AlbumResponseDto albumInfo) { Widget buildAlbumDateRange(Album album) {
String startDate = ""; final DateTime startDate = album.assets.first.createdAt;
DateTime parsedStartDate = final DateTime endDate = album.assets.last.createdAt; //Need default.
DateTime.parse(albumInfo.assets.first.createdAt); final String startDateText =
DateTime parsedEndDate = DateTime.parse( DateFormat(startDate.year == endDate.year ? 'LLL d' : 'LLL d, y')
albumInfo.assets.last.createdAt, .format(startDate);
); //Need default. final String endDateText = DateFormat('LLL d, y').format(endDate);
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);
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 16.0, left: 16.0,
top: 8.0, top: 8.0,
bottom: albumInfo.shared ? 0.0 : 8.0, bottom: album.shared ? 0.0 : 8.0,
), ),
child: Text( child: Text(
"$startDate-$endDate", "$startDateText-$endDateText",
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -153,15 +144,14 @@ class AlbumViewerPage extends HookConsumerWidget {
); );
} }
Widget buildHeader(AlbumResponseDto albumInfo) { Widget buildHeader(Album album) {
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildTitle(albumInfo), buildTitle(album),
if (albumInfo.assets.isNotEmpty == true) if (album.assets.isNotEmpty == true) buildAlbumDateRange(album),
buildAlbumDateRange(albumInfo), if (album.shared)
if (albumInfo.shared)
SizedBox( SizedBox(
height: 60, height: 60,
child: ListView.builder( 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 appSettingService = ref.watch(appSettingsServiceProvider);
final bool showStorageIndicator = final bool showStorageIndicator =
appSettingService.getSetting(AppSettingsEnum.storageIndicator); appSettingService.getSetting(AppSettingsEnum.storageIndicator);
if (albumInfo.assets.isNotEmpty) { if (album.assets.isNotEmpty) {
return SliverPadding( return SliverPadding(
padding: const EdgeInsets.only(top: 10.0), padding: const EdgeInsets.only(top: 10.0),
sliver: SliverGrid( sliver: SliverGrid(
@ -211,13 +201,12 @@ class AlbumViewerPage extends HookConsumerWidget {
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
return AlbumViewerThumbnail( return AlbumViewerThumbnail(
asset: Asset.remote(albumInfo.assets[index]), asset: album.assets[index],
assetList: assetList: album.assets,
albumInfo.assets.map((e) => Asset.remote(e)).toList(),
showStorageIndicator: showStorageIndicator, showStorageIndicator: showStorageIndicator,
); );
}, },
childCount: albumInfo.assetCount, childCount: album.assetCount,
), ),
), ),
); );
@ -225,7 +214,7 @@ class AlbumViewerPage extends HookConsumerWidget {
return const SliverToBoxAdapter(); return const SliverToBoxAdapter();
} }
Widget buildControlButton(AlbumResponseDto albumInfo) { Widget buildControlButton(Album album) {
return Padding( return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8), padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
child: SizedBox( child: SizedBox(
@ -235,13 +224,13 @@ class AlbumViewerPage extends HookConsumerWidget {
children: [ children: [
AlbumActionOutlinedButton( AlbumActionOutlinedButton(
iconData: Icons.add_photo_alternate_outlined, iconData: Icons.add_photo_alternate_outlined,
onPressed: () => onAddPhotosPressed(albumInfo), onPressed: () => onAddPhotosPressed(album),
labelText: "share_add_photos".tr(), labelText: "share_add_photos".tr(),
), ),
if (userId == albumInfo.ownerId) if (userId == album.ownerId)
AlbumActionOutlinedButton( AlbumActionOutlinedButton(
iconData: Icons.person_add_alt_rounded, iconData: Icons.person_add_alt_rounded,
onPressed: () => onAddUsersPressed(albumInfo), onPressed: () => onAddUsersPressed(album),
labelText: "album_viewer_page_share_add_users".tr(), labelText: "album_viewer_page_share_add_users".tr(),
), ),
], ],
@ -251,7 +240,10 @@ class AlbumViewerPage extends HookConsumerWidget {
} }
Future<bool> onWillPop() async { Future<bool> onWillPop() async {
final isMultiselectEnable = ref.read(assetSelectionProvider).selectedAssetsInAlbumViewer.isNotEmpty; final isMultiselectEnable = ref
.read(assetSelectionProvider)
.selectedAssetsInAlbumViewer
.isNotEmpty;
if (isMultiselectEnable) { if (isMultiselectEnable) {
ref.watch(assetSelectionProvider.notifier).removeAll(); ref.watch(assetSelectionProvider.notifier).removeAll();
return false; return false;
@ -260,7 +252,7 @@ class AlbumViewerPage extends HookConsumerWidget {
return true; return true;
} }
Widget buildBody(AlbumResponseDto albumInfo) { Widget buildBody(Album album) {
return WillPopScope( return WillPopScope(
onWillPop: onWillPop, onWillPop: onWillPop,
child: GestureDetector( child: GestureDetector(
@ -274,7 +266,7 @@ class AlbumViewerPage extends HookConsumerWidget {
child: CustomScrollView( child: CustomScrollView(
controller: scrollController, controller: scrollController,
slivers: [ slivers: [
buildHeader(albumInfo), buildHeader(album),
SliverPersistentHeader( SliverPersistentHeader(
pinned: true, pinned: true,
delegate: ImmichSliverPersistentAppBarDelegate( delegate: ImmichSliverPersistentAppBarDelegate(
@ -282,11 +274,11 @@ class AlbumViewerPage extends HookConsumerWidget {
maxHeight: 50, maxHeight: 50,
child: Container( child: Container(
color: Theme.of(context).scaffoldBackgroundColor, 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( return Scaffold(
appBar: albumInfo.when( appBar: album.when(
data: (AlbumResponseDto? data) { data: (Album? data) {
if (data != null) { if (data != null) {
return AlbumViewerAppbar( return AlbumViewerAppbar(
albumInfo: data, album: data,
userId: userId, userId: userId,
albumId: albumId,
); );
} }
return null; return null;
@ -309,7 +300,7 @@ class AlbumViewerPage extends HookConsumerWidget {
error: (e, _) => null, error: (e, _) => null,
loading: () => null, loading: () => null,
), ),
body: albumInfo.when( body: album.when(
data: (albumInfo) => albumInfo != null data: (albumInfo) => albumInfo != null
? buildBody(albumInfo) ? buildBody(albumInfo)
: const Center( : const Center(

View File

@ -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/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart'; import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart';
import 'package:immich_mobile/routing/router.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 { class LibraryPage extends HookConsumerWidget {
const LibraryPage({Key? key}) : super(key: key); const LibraryPage({Key? key}) : super(key: key);
@ -41,11 +41,11 @@ class LibraryPage extends HookConsumerWidget {
final selectedAlbumSortOrder = useState(0); final selectedAlbumSortOrder = useState(0);
List<AlbumResponseDto> sortedAlbums() { List<Album> sortedAlbums() {
if (selectedAlbumSortOrder.value == 0) { if (selectedAlbumSortOrder.value == 0) {
return albums.sortedBy((album) => album.createdAt).reversed.toList(); return albums.sortedBy((album) => album.createdAt).reversed.toList();
} }
return albums.sortedBy((album) => album.albumName); return albums.sortedBy((album) => album.name);
} }
Widget buildSortButton() { Widget buildSortButton() {

View File

@ -4,27 +4,28 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.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:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart';
class SelectAdditionalUserForSharingPage extends HookConsumerWidget { 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); : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
AsyncValue<List<UserResponseDto>> suggestedShareUsers = final AsyncValue<List<User>> suggestedShareUsers =
ref.watch(suggestedSharedUsersProvider); ref.watch(suggestedSharedUsersProvider);
final sharedUsersList = useState<Set<UserResponseDto>>({}); final sharedUsersList = useState<Set<User>>({});
addNewUsersHandler() { addNewUsersHandler() {
AutoRouter.of(context) AutoRouter.of(context)
.pop(sharedUsersList.value.map((e) => e.id).toList()); .pop(sharedUsersList.value.map((e) => e.id).toList());
} }
buildTileIcon(UserResponseDto user) { buildTileIcon(User user) {
if (sharedUsersList.value.contains(user)) { if (sharedUsersList.value.contains(user)) {
return CircleAvatar( return CircleAvatar(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
@ -42,7 +43,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
} }
} }
buildUserList(List<UserResponseDto> users) { buildUserList(List<User> users) {
List<Widget> usersChip = []; List<Widget> usersChip = [];
for (var user in sharedUsersList.value) { for (var user in sharedUsersList.value) {
@ -140,9 +141,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
), ),
body: suggestedShareUsers.when( body: suggestedShareUsers.when(
data: (users) { data: (users) {
for (var sharedUsers in albumInfo.sharedUsers) { for (var sharedUsers in album.sharedUsers) {
users.removeWhere( users.removeWhere(
(u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId, (u) => u.id == sharedUsers.id || u.id == album.ownerId,
); );
} }

View File

@ -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/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.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/routing/router.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart';
class SelectUserForSharingPage extends HookConsumerWidget { class SelectUserForSharingPage extends HookConsumerWidget {
const SelectUserForSharingPage({Key? key}) : super(key: key); const SelectUserForSharingPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final sharedUsersList = useState<Set<UserResponseDto>>({}); final sharedUsersList = useState<Set<User>>({});
AsyncValue<List<UserResponseDto>> suggestedShareUsers = AsyncValue<List<User>> suggestedShareUsers =
ref.watch(suggestedSharedUsersProvider); ref.watch(suggestedSharedUsersProvider);
createSharedAlbum() async { createSharedAlbum() async {
@ -25,7 +25,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
await ref.watch(sharedAlbumProvider.notifier).createSharedAlbum( await ref.watch(sharedAlbumProvider.notifier).createSharedAlbum(
ref.watch(albumTitleProvider), ref.watch(albumTitleProvider),
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum, ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
sharedUsersList.value.map((userInfo) => userInfo.id).toList(), sharedUsersList.value,
); );
if (newAlbum != null) { if (newAlbum != null) {
@ -44,7 +44,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
); );
} }
buildTileIcon(UserResponseDto user) { buildTileIcon(User user) {
if (sharedUsersList.value.contains(user)) { if (sharedUsersList.value.contains(user)) {
return CircleAvatar( return CircleAvatar(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
@ -62,7 +62,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
} }
} }
buildUserList(List<UserResponseDto> users) { buildUserList(List<User> users) {
List<Widget> usersChip = []; List<Widget> usersChip = [];
for (var user in sharedUsersList.value) { for (var user in sharedUsersList.value) {

View File

@ -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/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/ui/sharing_sliver_appbar.dart'; import 'package:immich_mobile/modules/album/ui/sharing_sliver_appbar.dart';
import 'package:immich_mobile/routing/router.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:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
class SharingPage extends HookConsumerWidget { class SharingPage extends HookConsumerWidget {
const SharingPage({Key? key}) : super(key: key); const SharingPage({Key? key}) : super(key: key);
@ -18,7 +18,7 @@ class SharingPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
var box = Hive.box(userInfoBox); var box = Hive.box(userInfoBox);
final List<AlbumResponseDto> sharedAlbums = ref.watch(sharedAlbumProvider); final List<Album> sharedAlbums = ref.watch(sharedAlbumProvider);
useEffect( useEffect(
() { () {
@ -52,7 +52,7 @@ class SharingPage extends HookConsumerWidget {
), ),
), ),
title: Text( title: Text(
sharedAlbums[index].albumName, sharedAlbums[index].name,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(

View File

@ -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/album/ui/add_to_album_sliverlist.dart';
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart'; import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.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 { class ControlBottomAppBar extends ConsumerWidget {
final Function onShare; final Function onShare;
final Function onDelete; final Function onDelete;
final Function(AlbumResponseDto album) onAddToAlbum; final Function(Album album) onAddToAlbum;
final void Function() onCreateNewAlbum; final void Function() onCreateNewAlbum;
final List<AlbumResponseDto> albums; final List<Album> albums;
final List<AlbumResponseDto> sharedAlbums; final List<Album> sharedAlbums;
const ControlBottomAppBar({ const ControlBottomAppBar({
Key? key, Key? key,

View File

@ -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/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/routing/router.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/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.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/services/share.service.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:openapi/api.dart';
class HomePage extends HookConsumerWidget { class HomePage extends HookConsumerWidget {
const HomePage({Key? key}) : super(key: key); const HomePage({Key? key}) : super(key: key);
@ -102,14 +102,14 @@ class HomePage extends HookConsumerWidget {
return assets; return assets;
} }
void onAddToAlbum(AlbumResponseDto album) async { void onAddToAlbum(Album album) async {
final Iterable<Asset> assets = remoteOnlySelection(); final Iterable<Asset> assets = remoteOnlySelection();
if (assets.isEmpty) { if (assets.isEmpty) {
return; return;
} }
final result = await albumService.addAdditionalAssetToAlbum( final result = await albumService.addAdditionalAssetToAlbum(
assets, assets,
album.id, album,
); );
if (result != null) { if (result != null) {
@ -118,7 +118,7 @@ class HomePage extends HookConsumerWidget {
context: context, context: context,
msg: "home_page_add_to_album_conflicts".tr( msg: "home_page_add_to_album_conflicts".tr(
namedArgs: { namedArgs: {
"album": album.albumName, "album": album.name,
"added": result.successfullyAdded.toString(), "added": result.successfullyAdded.toString(),
"failed": result.alreadyInAlbum.length.toString() "failed": result.alreadyInAlbum.length.toString()
}, },
@ -129,7 +129,7 @@ class HomePage extends HookConsumerWidget {
context: context, context: context,
msg: "home_page_add_to_album_success".tr( msg: "home_page_add_to_album_success".tr(
namedArgs: { namedArgs: {
"album": album.albumName, "album": album.name,
"added": result.successfullyAdded.toString(), "added": result.successfullyAdded.toString(),
}, },
), ),

View File

@ -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/modules/settings/views/settings_page.dart';
import 'package:immich_mobile/routing/auth_guard.dart'; import 'package:immich_mobile/routing/auth_guard.dart';
import 'package:immich_mobile/shared/models/asset.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/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.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/app_log_page.dart';
import 'package:immich_mobile/shared/views/splash_screen.dart'; import 'package:immich_mobile/shared/views/splash_screen.dart';
import 'package:immich_mobile/shared/views/tab_controller_page.dart'; import 'package:immich_mobile/shared/views/tab_controller_page.dart';
import 'package:openapi/api.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
part 'router.gr.dart'; part 'router.gr.dart';

View File

@ -108,7 +108,7 @@ class _$AppRouter extends RootStackRouter {
return CustomPage<List<String>?>( return CustomPage<List<String>?>(
routeData: routeData, routeData: routeData,
child: SelectAdditionalUserForSharingPage( child: SelectAdditionalUserForSharingPage(
key: args.key, albumInfo: args.albumInfo), key: args.key, album: args.album),
transitionsBuilder: TransitionsBuilders.slideBottom, transitionsBuilder: TransitionsBuilders.slideBottom,
opaque: true, opaque: true,
barrierDismissible: false); barrierDismissible: false);
@ -447,27 +447,26 @@ class AlbumViewerRouteArgs {
/// [SelectAdditionalUserForSharingPage] /// [SelectAdditionalUserForSharingPage]
class SelectAdditionalUserForSharingRoute class SelectAdditionalUserForSharingRoute
extends PageRouteInfo<SelectAdditionalUserForSharingRouteArgs> { extends PageRouteInfo<SelectAdditionalUserForSharingRouteArgs> {
SelectAdditionalUserForSharingRoute( SelectAdditionalUserForSharingRoute({Key? key, required Album album})
{Key? key, required AlbumResponseDto albumInfo})
: super(SelectAdditionalUserForSharingRoute.name, : super(SelectAdditionalUserForSharingRoute.name,
path: '/select-additional-user-for-sharing-page', path: '/select-additional-user-for-sharing-page',
args: SelectAdditionalUserForSharingRouteArgs( args: SelectAdditionalUserForSharingRouteArgs(
key: key, albumInfo: albumInfo)); key: key, album: album));
static const String name = 'SelectAdditionalUserForSharingRoute'; static const String name = 'SelectAdditionalUserForSharingRoute';
} }
class SelectAdditionalUserForSharingRouteArgs { class SelectAdditionalUserForSharingRouteArgs {
const SelectAdditionalUserForSharingRouteArgs( const SelectAdditionalUserForSharingRouteArgs(
{this.key, required this.albumInfo}); {this.key, required this.album});
final Key? key; final Key? key;
final AlbumResponseDto albumInfo; final Album album;
@override @override
String toString() { String toString() {
return 'SelectAdditionalUserForSharingRouteArgs{key: $key, albumInfo: $albumInfo}'; return 'SelectAdditionalUserForSharingRouteArgs{key: $key, album: $album}';
} }
} }

View 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;
}

View 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;
}
}

View File

@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.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/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart'; import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/utils/files_helper.dart'; import 'package:immich_mobile/utils/files_helper.dart';
@ -19,9 +20,10 @@ class UserService {
UserService(this._apiService); UserService(this._apiService);
Future<List<UserResponseDto>?> getAllUsersInfo({required bool isAll}) async { Future<List<User>?> getAllUsers({required bool isAll}) async {
try { try {
return await _apiService.userApi.getAllUsers(isAll); final dto = await _apiService.userApi.getAllUsers(isAll);
return dto?.map(User.fromDto).toList();
} catch (e) { } catch (e) {
debugPrint("Error [getAllUsersInfo] ${e.toString()}"); debugPrint("Error [getAllUsersInfo] ${e.toString()}");
return null; return null;

View File

@ -1,4 +1,5 @@
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@ -15,7 +16,7 @@ String getThumbnailCacheKey(
final Asset asset, { final Asset asset, {
ThumbnailFormat type = ThumbnailFormat.WEBP, ThumbnailFormat type = ThumbnailFormat.WEBP,
}) { }) {
return _getThumbnailCacheKey(asset.id, type); return _getThumbnailCacheKey(asset.remoteId!, type);
} }
String _getThumbnailCacheKey(final String id, final ThumbnailFormat type) { String _getThumbnailCacheKey(final String id, final ThumbnailFormat type) {
@ -27,7 +28,7 @@ String _getThumbnailCacheKey(final String id, final ThumbnailFormat type) {
} }
String getAlbumThumbnailUrl( String getAlbumThumbnailUrl(
final AlbumResponseDto album, { final Album album, {
ThumbnailFormat type = ThumbnailFormat.WEBP, ThumbnailFormat type = ThumbnailFormat.WEBP,
}) { }) {
if (album.albumThumbnailAssetId == null) { if (album.albumThumbnailAssetId == null) {
@ -37,7 +38,7 @@ String getAlbumThumbnailUrl(
} }
String getAlbumThumbNailCacheKey( String getAlbumThumbNailCacheKey(
final AlbumResponseDto album, { final Album album, {
ThumbnailFormat type = ThumbnailFormat.WEBP, ThumbnailFormat type = ThumbnailFormat.WEBP,
}) { }) {
if (album.albumThumbnailAssetId == null) { if (album.albumThumbnailAssetId == null) {