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

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

View file

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

View file

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

View file

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

View file

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

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/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}',
);
}
}

View file

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

View file

@ -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,
),

View file

@ -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,
),

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/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(

View file

@ -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') {

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

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

View file

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

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

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

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

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

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/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';

View file

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

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

View file

@ -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) {