1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-01 08:31:59 +00:00

refactor(mobile): use repositories in a number of services (#12891)

* UserService
* PartnerService
* HashService
* MemoryService
* PersonService
* SearchService
* StackService
This commit is contained in:
Fynn Petersen-Frey 2024-09-24 14:50:21 +02:00 committed by GitHub
parent e0fa3cdbc7
commit 202082f62e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 409 additions and 206 deletions

View file

@ -69,14 +69,14 @@ custom_lint:
- integration_test/test_utils/general_helper.dart - integration_test/test_utils/general_helper.dart
- lib/main.dart - lib/main.dart
- lib/routing/router.dart - lib/routing/router.dart
- lib/utils/{db,image_url_builder,migration,renderlist_generator}.dart - lib/utils/{db,migration,renderlist_generator}.dart
- test/**.dart - test/**.dart
# refactor to make the providers and services testable # refactor to make the providers and services testable
- lib/pages/common/{album_asset_selection,gallery_viewer}.page.dart - lib/pages/common/album_asset_selection.page.dart
- lib/providers/{archive,asset,authentication,db,favorite,partner,trash,user}.provider.dart - lib/providers/{archive,asset,authentication,db,favorite,partner,trash,user}.provider.dart
- lib/providers/{album/album,album/shared_album,asset_viewer/asset_stack,asset_viewer/render_list,backup/backup,backup/manual_upload,search/all_motion_photos,search/recently_added_asset}.provider.dart - lib/providers/{album/album,album/shared_album,asset_viewer/asset_stack,asset_viewer/render_list,backup/backup,backup/manual_upload,search/all_motion_photos,search/recently_added_asset}.provider.dart
- lib/services/{asset,background,backup,hash,immich_logger,memory,partner,person,search,stack,sync,user}.service.dart - lib/services/{asset,background,backup,immich_logger,sync}.service.dart
- lib/widgets/asset_grid/{asset_grid_data_structure,thumbnail_image}.dart - lib/widgets/asset_grid/asset_grid_data_structure.dart
- import_rule_openapi: - import_rule_openapi:
message: openapi must only be used through ApiRepositories message: openapi must only be used through ApiRepositories
@ -90,18 +90,16 @@ custom_lint:
- test/modules/utils/openapi_patching_test.dart # filename is self-explanatory... - test/modules/utils/openapi_patching_test.dart # filename is self-explanatory...
# refactor # refactor
- lib/models/map/map_marker.model.dart - lib/models/map/map_marker.model.dart
- lib/models/search/search_filter.model.dart
- lib/models/server_info/server_{config,disk_info,features,version}.model.dart - lib/models/server_info/server_{config,disk_info,features,version}.model.dart
- lib/models/shared_link/shared_link.model.dart - lib/models/shared_link/shared_link.model.dart
- lib/pages/search/search_input.page.dart
- lib/providers/asset_viewer/asset_people.provider.dart - lib/providers/asset_viewer/asset_people.provider.dart
- lib/providers/authentication.provider.dart - lib/providers/authentication.provider.dart
- lib/providers/image/immich_remote_{image,thumbnail}_provider.dart - lib/providers/image/immich_remote_{image,thumbnail}_provider.dart
- lib/providers/map/map_state.provider.dart - lib/providers/map/map_state.provider.dart
- lib/providers/search/{people,search,search_filter}.provider.dart - lib/providers/search/{search,search_filter}.provider.dart
- lib/providers/websocket.provider.dart - lib/providers/websocket.provider.dart
- lib/routing/auth_guard.dart - lib/routing/auth_guard.dart
- lib/services/{api,asset,backup,memory,oauth,partner,person,search,shared_link,stack,trash,user}.service.dart - lib/services/{api,asset,backup,memory,oauth,search,shared_link,stack,trash}.service.dart
- lib/widgets/album/album_thumbnail_listtile.dart - lib/widgets/album/album_thumbnail_listtile.dart
- lib/widgets/forms/login/login_form.dart - lib/widgets/forms/login/login_form.dart
- lib/widgets/search/search_filter/{camera_picker,location_picker,people_picker}.dart - lib/widgets/search/search_filter/{camera_picker,location_picker,people_picker}.dart

View file

@ -0,0 +1 @@
const int noDbId = -9223372036854775808; // from Isar

View file

@ -1,5 +1,6 @@
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/device_asset.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IAssetRepository { abstract interface class IAssetRepository {
@ -12,6 +13,7 @@ abstract interface class IAssetRepository {
bool? remote, bool? remote,
int limit = 100, int limit = 100,
}); });
Future<List<Asset>> updateAll(List<Asset> assets);
Future<List<Asset>> getMatches({ Future<List<Asset>> getMatches({
required List<Asset> assets, required List<Asset> assets,
@ -19,4 +21,7 @@ abstract interface class IAssetRepository {
bool? remote, bool? remote,
int limit = 100, int limit = 100,
}); });
Future<List<DeviceAsset?>> getDeviceAssetsById(List<Object> ids);
Future<void> upsertDeviceAssets(List<DeviceAsset> deviceAssets);
} }

View file

@ -13,4 +13,6 @@ abstract interface class IAssetApiRepository {
}); });
// Future<void> delete(String id); // Future<void> delete(String id);
Future<List<Asset>> search({List<String> personIds = const []});
} }

View file

@ -0,0 +1,13 @@
import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IPartnerApiRepository {
Future<List<User>> getAll(Direction direction);
Future<User> create(String id);
Future<User> update(String id, {required bool inTimeline});
Future<void> delete(String id);
}
enum Direction {
sharedWithMe,
sharedByMe,
}

View file

@ -0,0 +1,22 @@
abstract interface class IPersonApiRepository {
Future<List<Person>> getAll();
Future<Person> update(String id, {String? name});
}
class Person {
Person({
required this.id,
required this.isHidden,
required this.name,
required this.thumbnailPath,
this.birthDate,
this.updatedAt,
});
final String id;
final DateTime? birthDate;
final bool isHidden;
final String name;
final String thumbnailPath;
final DateTime? updatedAt;
}

View file

@ -3,4 +3,6 @@ import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IUserRepository { abstract interface class IUserRepository {
Future<List<User>> getByIds(List<String> ids); Future<List<User>> getByIds(List<String> ids);
Future<User?> get(String id); Future<User?> get(String id);
Future<List<User>> getAll({bool self = true});
Future<User> update(User user);
} }

View file

@ -0,0 +1,11 @@
import 'dart:typed_data';
import 'package:immich_mobile/entities/user.entity.dart';
abstract interface class IUserApiRepository {
Future<List<User>> getAll();
Future<({String profileImagePath})> createProfileImage({
required String name,
required Uint8List data,
});
}

View file

@ -2,7 +2,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:openapi/api.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart';
class SearchLocationFilter { class SearchLocationFilter {
String? country; String? country;
@ -235,7 +235,7 @@ class SearchDisplayFilters {
class SearchFilter { class SearchFilter {
String? context; String? context;
String? filename; String? filename;
Set<PersonResponseDto> people; Set<Person> people;
SearchLocationFilter location; SearchLocationFilter location;
SearchCameraFilter camera; SearchCameraFilter camera;
SearchDateFilter date; SearchDateFilter date;
@ -258,7 +258,7 @@ class SearchFilter {
SearchFilter copyWith({ SearchFilter copyWith({
String? context, String? context,
String? filename, String? filename,
Set<PersonResponseDto>? people, Set<Person>? people,
SearchLocationFilter? location, SearchLocationFilter? location,
SearchCameraFilter? camera, SearchCameraFilter? camera,
SearchDateFilter? date, SearchDateFilter? date,

View file

@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/pages/common/video_viewer.page.dart'; import 'package:immich_mobile/pages/common/video_viewer.page.dart';
@ -30,7 +31,6 @@ import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart'; import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart';
import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart'; import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart';
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart'; import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart';
import 'package:isar/isar.dart';
@RoutePage() @RoutePage()
// ignore: must_be_immutable // ignore: must_be_immutable
@ -73,7 +73,7 @@ class GalleryViewerPage extends HookConsumerWidget {
: <Asset>[]; : <Asset>[];
final stackElements = showStack ? [currentAsset, ...stack] : <Asset>[]; final stackElements = showStack ? [currentAsset, ...stack] : <Asset>[];
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = currentAsset.id == Isar.autoIncrement; final isFromDto = currentAsset.id == noDbId;
Asset asset = stackIndex.value == -1 Asset asset = stackIndex.value == -1
? currentAsset ? currentAsset

View file

@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart'; import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
@ -19,7 +20,6 @@ import 'package:immich_mobile/widgets/search/search_filter/media_type_picker.dar
import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart'; import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart'; import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart'; import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
import 'package:openapi/api.dart';
@RoutePage() @RoutePage()
class SearchInputPage extends HookConsumerWidget { class SearchInputPage extends HookConsumerWidget {
@ -110,7 +110,7 @@ class SearchInputPage extends HookConsumerWidget {
} }
showPeoplePicker() { showPeoplePicker() {
handleOnSelect(Set<PersonResponseDto> value) { handleOnSelect(Set<Person> value) {
filter.value = filter.value.copyWith( filter.value = filter.value.copyWith(
people: value, people: value,
); );

View file

@ -5,5 +5,5 @@ import 'package:immich_mobile/services/user.service.dart';
final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) { final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) {
UserService userService = ref.watch(userServiceProvider); UserService userService = ref.watch(userServiceProvider);
return userService.getUsersInDb(); return userService.getUsers();
}); });

Binary file not shown.

View file

@ -1,14 +1,14 @@
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/services/person.service.dart'; import 'package:immich_mobile/services/person.service.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:openapi/api.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'people.provider.g.dart'; part 'people.provider.g.dart';
@riverpod @riverpod
Future<List<PersonResponseDto>> getAllPeople( Future<List<Person>> getAllPeople(
GetAllPeopleRef ref, GetAllPeopleRef ref,
) async { ) async {
final PersonService personService = ref.read(personServiceProvider); final PersonService personService = ref.read(personServiceProvider);

Binary file not shown.

View file

@ -1,6 +1,11 @@
import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/device_asset.entity.dart';
import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
@ -69,6 +74,12 @@ class AssetRepository implements IAssetRepository {
return query.limit(limit).findAll(); return query.limit(limit).findAll();
} }
@override
Future<List<Asset>> updateAll(List<Asset> assets) async {
await _db.writeTxn(() => _db.assets.putAll(assets));
return assets;
}
@override @override
Future<List<Asset>> getMatches({ Future<List<Asset>> getMatches({
required List<Asset> assets, required List<Asset> assets,
@ -86,6 +97,20 @@ class AssetRepository implements IAssetRepository {
} }
return _getMatchesImpl(query, ownerId, assets, limit); return _getMatchesImpl(query, ownerId, assets, limit);
} }
@override
Future<List<DeviceAsset?>> getDeviceAssetsById(List<Object> ids) =>
Platform.isAndroid
? _db.androidDeviceAssets.getAll(ids.cast())
: _db.iOSDeviceAssets.getAllById(ids.cast());
@override
Future<void> upsertDeviceAssets(List<DeviceAsset> deviceAssets) =>
_db.writeTxn(
() => Platform.isAndroid
? _db.androidDeviceAssets.putAll(deviceAssets.cast())
: _db.iOSDeviceAssets.putAll(deviceAssets.cast()),
);
} }
Future<List<Asset>> _getMatchesImpl( Future<List<Asset>> _getMatchesImpl(

View file

@ -6,14 +6,18 @@ import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
final assetApiRepositoryProvider = Provider( final assetApiRepositoryProvider = Provider(
(ref) => AssetApiRepository(ref.watch(apiServiceProvider).assetsApi), (ref) => AssetApiRepository(
ref.watch(apiServiceProvider).assetsApi,
ref.watch(apiServiceProvider).searchApi,
),
); );
class AssetApiRepository extends BaseApiRepository class AssetApiRepository extends BaseApiRepository
implements IAssetApiRepository { implements IAssetApiRepository {
final AssetsApi _api; final AssetsApi _api;
final SearchApi _searchApi;
AssetApiRepository(this._api); AssetApiRepository(this._api, this._searchApi);
@override @override
Future<Asset> update(String id, {String? description}) async { Future<Asset> update(String id, {String? description}) async {
@ -22,4 +26,27 @@ class AssetApiRepository extends BaseApiRepository
); );
return Asset.remote(response); return Asset.remote(response);
} }
@override
Future<List<Asset>> search({List<String> personIds = const []}) async {
// TODO this always fetches all assets, change API and usage to actually do pagination
final List<Asset> result = [];
bool hasNext = true;
int currentPage = 1;
while (hasNext) {
final response = await checkNull(
_searchApi.searchMetadata(
MetadataSearchDto(
personIds: personIds,
page: currentPage,
size: 1000,
),
),
);
result.addAll(response.assets.items.map(Asset.remote));
hasNext = response.assets.nextPage != null;
currentPage++;
}
return result;
}
} }

View file

@ -0,0 +1,51 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final partnerApiRepositoryProvider = Provider(
(ref) => PartnerApiRepository(
ref.watch(apiServiceProvider).partnersApi,
),
);
class PartnerApiRepository extends BaseApiRepository
implements IPartnerApiRepository {
final PartnersApi _api;
PartnerApiRepository(this._api);
@override
Future<List<User>> getAll(Direction direction) async {
final response = await checkNull(
_api.getPartners(
direction == Direction.sharedByMe
? PartnerDirection.by
: PartnerDirection.with_,
),
);
return response.map(User.fromPartnerDto).toList();
}
@override
Future<User> create(String id) async {
final dto = await checkNull(_api.createPartner(id));
return User.fromPartnerDto(dto);
}
@override
Future<void> delete(String id) => checkNull(_api.removePartner(id));
@override
Future<User> update(String id, {required bool inTimeline}) async {
final dto = await checkNull(
_api.updatePartner(
id,
UpdatePartnerDto(inTimeline: inTimeline),
),
);
return User.fromPartnerDto(dto);
}
}

View file

@ -0,0 +1,38 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final personApiRepositoryProvider = Provider(
(ref) => PersonApiRepository(ref.watch(apiServiceProvider).peopleApi),
);
class PersonApiRepository extends BaseApiRepository
implements IPersonApiRepository {
final PeopleApi _api;
PersonApiRepository(this._api);
@override
Future<List<Person>> getAll() async {
final dto = await checkNull(_api.getAllPeople());
return dto.people.map(_toPerson).toList();
}
@override
Future<Person> update(String id, {String? name}) async {
final dto = await checkNull(
_api.updatePerson(id, PersonUpdateDto(name: name)),
);
return _toPerson(dto);
}
static Person _toPerson(PersonResponseDto dto) => Person(
birthDate: dto.birthDate,
id: dto.id,
isHidden: dto.isHidden,
name: dto.name,
thumbnailPath: dto.thumbnailPath,
);
}

View file

@ -1,4 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/user.interface.dart'; import 'package:immich_mobile/interfaces/user.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
@ -20,4 +21,19 @@ class UserRepository implements IUserRepository {
@override @override
Future<User?> get(String id) => _db.users.getById(id); Future<User?> get(String id) => _db.users.getById(id);
@override
Future<List<User>> getAll({bool self = true}) {
if (self) {
return _db.users.where().findAll();
}
final int userId = Store.get(StoreKey.currentUser).isarId;
return _db.users.where().isarIdNotEqualTo(userId).findAll();
}
@override
Future<User> update(User user) async {
await _db.writeTxn(() => _db.users.put(user));
return user;
}
} }

View file

@ -0,0 +1,41 @@
import 'dart:typed_data';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/user_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/base_api.repository.dart';
import 'package:openapi/api.dart';
final userApiRepositoryProvider = Provider(
(ref) => UserApiRepository(
ref.watch(apiServiceProvider).usersApi,
),
);
class UserApiRepository extends BaseApiRepository
implements IUserApiRepository {
final UsersApi _api;
UserApiRepository(this._api);
@override
Future<List<User>> getAll() async {
final dto = await checkNull(_api.searchUsers());
return dto.map(User.fromSimpleUserDto).toList();
}
@override
Future<({String profileImagePath})> createProfileImage({
required String name,
required Uint8List data,
}) async {
final response = await checkNull(
_api.createProfileImage(
MultipartFile.fromBytes('file', data, filename: name),
),
);
return (profileImagePath: response.profileImagePath);
}
}

View file

@ -18,7 +18,9 @@ import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/repositories/user.repository.dart'; import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:immich_mobile/repositories/user_api.repository.dart';
import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/entity.service.dart';
import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/services/hash.service.dart';
@ -30,7 +32,6 @@ import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/partner.service.dart';
import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/sync.service.dart';
import 'package:immich_mobile/services/user.service.dart'; import 'package:immich_mobile/services/user.service.dart';
import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/backup_progress.dart';
@ -362,16 +363,20 @@ class BackgroundService {
apiService.setAccessToken(Store.get(StoreKey.accessToken)); apiService.setAccessToken(Store.get(StoreKey.accessToken));
AppSettingsService settingService = AppSettingsService(); AppSettingsService settingService = AppSettingsService();
AppSettingsService settingsService = AppSettingsService(); AppSettingsService settingsService = AppSettingsService();
PartnerService partnerService = PartnerService(apiService, db);
AlbumRepository albumRepository = AlbumRepository(db); AlbumRepository albumRepository = AlbumRepository(db);
AssetRepository assetRepository = AssetRepository(db); AssetRepository assetRepository = AssetRepository(db);
BackupRepository backupAlbumRepository = BackupRepository(db); BackupRepository backupAlbumRepository = BackupRepository(db);
AlbumMediaRepository albumMediaRepository = AlbumMediaRepository(); AlbumMediaRepository albumMediaRepository = AlbumMediaRepository();
FileMediaRepository fileMediaRepository = FileMediaRepository(); FileMediaRepository fileMediaRepository = FileMediaRepository();
UserRepository userRepository = UserRepository(db); UserRepository userRepository = UserRepository(db);
UserApiRepository userApiRepository =
UserApiRepository(apiService.usersApi);
AlbumApiRepository albumApiRepository = AlbumApiRepository albumApiRepository =
AlbumApiRepository(apiService.albumsApi); AlbumApiRepository(apiService.albumsApi);
HashService hashService = HashService(db, this, albumMediaRepository); PartnerApiRepository partnerApiRepository =
PartnerApiRepository(apiService.partnersApi);
HashService hashService =
HashService(assetRepository, this, albumMediaRepository);
EntityService entityService = EntityService entityService =
EntityService(assetRepository, userRepository); EntityService(assetRepository, userRepository);
SyncService syncSerive = SyncService( SyncService syncSerive = SyncService(
@ -381,8 +386,12 @@ class BackgroundService {
albumMediaRepository, albumMediaRepository,
albumApiRepository, albumApiRepository,
); );
UserService userService = UserService userService = UserService(
UserService(apiService, db, syncSerive, partnerService); partnerApiRepository,
userApiRepository,
userRepository,
syncSerive,
);
AlbumService albumService = AlbumService( AlbumService albumService = AlbumService(
userService, userService,
syncSerive, syncSerive,

View file

@ -4,20 +4,24 @@ import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/android_device_asset.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/device_asset.entity.dart'; import 'package:immich_mobile/entities/device_asset.entity.dart';
import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/extensions/string_extensions.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
class HashService { class HashService {
HashService(this._db, this._backgroundService, this._albumMediaRepository); HashService(
final Isar _db; this._assetRepository,
this._backgroundService,
this._albumMediaRepository,
);
final IAssetRepository _assetRepository;
final BackgroundService _backgroundService; final BackgroundService _backgroundService;
final IAlbumMediaRepository _albumMediaRepository; final IAlbumMediaRepository _albumMediaRepository;
final _log = Logger('HashService'); final _log = Logger('HashService');
@ -55,7 +59,8 @@ class HashService {
final ids = assets final ids = assets
.map(Platform.isAndroid ? (a) => a.localId!.toInt() : (a) => a.localId!) .map(Platform.isAndroid ? (a) => a.localId!.toInt() : (a) => a.localId!)
.toList(); .toList();
final List<DeviceAsset?> hashes = await _lookupHashes(ids); final List<DeviceAsset?> hashes =
await _assetRepository.getDeviceAssetsById(ids);
final List<DeviceAsset> toAdd = []; final List<DeviceAsset> toAdd = [];
final List<String> toHash = []; final List<String> toHash = [];
@ -106,12 +111,6 @@ class HashService {
return _getHashedAssets(assets, hashes); return _getHashedAssets(assets, hashes);
} }
/// Lookup hashes of assets by their local ID
Future<List<DeviceAsset?>> _lookupHashes(List<Object> ids) =>
Platform.isAndroid
? _db.androidDeviceAssets.getAll(ids.cast())
: _db.iOSDeviceAssets.getAllById(ids.cast());
/// Processes a batch of files and saves any successfully hashed /// Processes a batch of files and saves any successfully hashed
/// values to the DB table. /// values to the DB table.
Future<void> _processBatch( Future<void> _processBatch(
@ -131,11 +130,7 @@ class HashService {
final validHashes = anyNull final validHashes = anyNull
? toAdd.where((e) => e.hash.length == 20).toList(growable: false) ? toAdd.where((e) => e.hash.length == 20).toList(growable: false)
: toAdd; : toAdd;
await _db.writeTxn( await _assetRepository.upsertDeviceAssets(validHashes);
() => Platform.isAndroid
? _db.androidDeviceAssets.putAll(validHashes.cast())
: _db.iOSDeviceAssets.putAll(validHashes.cast()),
);
_log.fine("Hashed ${validHashes.length}/${toHash.length} assets"); _log.fine("Hashed ${validHashes.length}/${toHash.length} assets");
} }
@ -168,7 +163,7 @@ class HashService {
final hashServiceProvider = Provider( final hashServiceProvider = Provider(
(ref) => HashService( (ref) => HashService(
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
ref.watch(backgroundServiceProvider), ref.watch(backgroundServiceProvider),
ref.watch(albumMediaRepositoryProvider), ref.watch(albumMediaRepositoryProvider),
), ),

View file

@ -1,18 +1,17 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/models/memories/memory.model.dart'; import 'package:immich_mobile/models/memories/memory.model.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
final memoryServiceProvider = StateProvider<MemoryService>((ref) { final memoryServiceProvider = StateProvider<MemoryService>((ref) {
return MemoryService( return MemoryService(
ref.watch(apiServiceProvider), ref.watch(apiServiceProvider),
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
); );
}); });
@ -20,9 +19,9 @@ class MemoryService {
final log = Logger("MemoryService"); final log = Logger("MemoryService");
final ApiService _apiService; final ApiService _apiService;
final Isar _db; final IAssetRepository _assetRepository;
MemoryService(this._apiService, this._db); MemoryService(this._apiService, this._assetRepository);
Future<List<Memory>?> getMemoryLane() async { Future<List<Memory>?> getMemoryLane() async {
try { try {
@ -39,7 +38,7 @@ class MemoryService {
List<Memory> memories = []; List<Memory> memories = [];
for (final MemoryLaneResponseDto(:yearsAgo, :assets) in data) { for (final MemoryLaneResponseDto(:yearsAgo, :assets) in data) {
final dbAssets = final dbAssets =
await _db.assets.getAllByRemoteId(assets.map((e) => e.id)); await _assetRepository.getAllByRemoteId(assets.map((e) => e.id));
if (dbAssets.isNotEmpty) { if (dbAssets.isNotEmpty) {
final String title = yearsAgo <= 1 final String title = yearsAgo <= 1
? 'memories_year_ago'.tr() ? 'memories_year_ago'.tr()

View file

@ -1,43 +1,33 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/interfaces/user.interface.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:isar/isar.dart'; import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
final partnerServiceProvider = Provider( final partnerServiceProvider = Provider(
(ref) => PartnerService( (ref) => PartnerService(
ref.watch(apiServiceProvider), ref.watch(partnerApiRepositoryProvider),
ref.watch(dbProvider), ref.watch(userRepositoryProvider),
), ),
); );
class PartnerService { class PartnerService {
final ApiService _apiService; final IPartnerApiRepository _partnerApiRepository;
final Isar _db; final IUserRepository _userRepository;
final Logger _log = Logger("PartnerService"); final Logger _log = Logger("PartnerService");
PartnerService(this._apiService, this._db); PartnerService(
this._partnerApiRepository,
Future<List<User>?> getPartners(PartnerDirection direction) async { this._userRepository,
try { );
final userDtos = await _apiService.partnersApi.getPartners(direction);
if (userDtos != null) {
return userDtos.map((u) => User.fromPartnerDto(u)).toList();
}
} catch (e) {
_log.warning("Failed to get partners for direction $direction", e);
}
return null;
}
Future<bool> removePartner(User partner) async { Future<bool> removePartner(User partner) async {
try { try {
await _apiService.partnersApi.removePartner(partner.id); await _partnerApiRepository.delete(partner.id);
partner.isPartnerSharedBy = false; partner.isPartnerSharedBy = false;
await _db.writeTxn(() => _db.users.put(partner)); await _userRepository.update(partner);
} catch (e) { } catch (e) {
_log.warning("Failed to remove partner ${partner.id}", e); _log.warning("Failed to remove partner ${partner.id}", e);
return false; return false;
@ -47,12 +37,10 @@ class PartnerService {
Future<bool> addPartner(User partner) async { Future<bool> addPartner(User partner) async {
try { try {
final dto = await _apiService.partnersApi.createPartner(partner.id); await _partnerApiRepository.create(partner.id);
if (dto != null) {
partner.isPartnerSharedBy = true; partner.isPartnerSharedBy = true;
await _db.writeTxn(() => _db.users.put(partner)); await _userRepository.update(partner);
return true; return true;
}
} catch (e) { } catch (e) {
_log.warning("Failed to add partner ${partner.id}", e); _log.warning("Failed to add partner ${partner.id}", e);
} }
@ -61,13 +49,13 @@ class PartnerService {
Future<bool> updatePartner(User partner, {required bool inTimeline}) async { Future<bool> updatePartner(User partner, {required bool inTimeline}) async {
try { try {
final dto = await _apiService.partnersApi final dto = await _partnerApiRepository.update(
.updatePartner(partner.id, UpdatePartnerDto(inTimeline: inTimeline)); partner.id,
if (dto != null) { inTimeline: inTimeline,
partner.inTimeline = dto.inTimeline ?? partner.inTimeline; );
await _db.writeTxn(() => _db.users.put(partner)); partner.inTimeline = dto.inTimeline;
await _userRepository.update(partner);
return true; return true;
}
} catch (e) { } catch (e) {
_log.warning("Failed to update partner ${partner.id}", e); _log.warning("Failed to update partner ${partner.id}", e);
} }

View file

@ -1,29 +1,37 @@
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:isar/isar.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/person_api.repository.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'person.service.g.dart'; part 'person.service.g.dart';
@riverpod @riverpod
PersonService personService(PersonServiceRef ref) => PersonService personService(PersonServiceRef ref) => PersonService(
PersonService(ref.read(apiServiceProvider), ref.read(dbProvider)); ref.watch(personApiRepositoryProvider),
ref.watch(assetApiRepositoryProvider),
ref.read(assetRepositoryProvider),
);
class PersonService { class PersonService {
final Logger _log = Logger("PersonService"); final Logger _log = Logger("PersonService");
final ApiService _apiService; final IPersonApiRepository _personApiRepository;
final Isar _db; final IAssetApiRepository _assetApiRepository;
final IAssetRepository _assetRepository;
PersonService(this._apiService, this._db); PersonService(
this._personApiRepository,
this._assetApiRepository,
this._assetRepository,
);
Future<List<PersonResponseDto>> getAllPeople() async { Future<List<Person>> getAllPeople() async {
try { try {
final peopleResponseDto = await _apiService.peopleApi.getAllPeople(); return await _personApiRepository.getAll();
return peopleResponseDto?.people ?? [];
} catch (error, stack) { } catch (error, stack) {
_log.severe("Error while fetching curated people", error, stack); _log.severe("Error while fetching curated people", error, stack);
return []; return [];
@ -31,50 +39,19 @@ class PersonService {
} }
Future<List<Asset>> getPersonAssets(String id) async { Future<List<Asset>> getPersonAssets(String id) async {
List<Asset> result = [];
var hasNext = true;
var currentPage = 1;
try { try {
while (hasNext) { final assets = await _assetApiRepository.search(personIds: [id]);
final response = await _apiService.searchApi.searchMetadata( return await _assetRepository
MetadataSearchDto( .getAllByRemoteId(assets.map((a) => a.remoteId!));
personIds: [id],
page: currentPage,
size: 1000,
),
);
if (response == null) {
break;
}
if (response.assets.nextPage == null) {
hasNext = false;
}
final assets = response.assets.items;
final mapAssets =
await _db.assets.getAllByRemoteId(assets.map((e) => e.id));
result.addAll(mapAssets);
currentPage++;
}
} catch (error, stack) { } catch (error, stack) {
_log.severe("Error while fetching person assets", error, stack); _log.severe("Error while fetching person assets", error, stack);
} }
return [];
return result;
} }
Future<PersonResponseDto?> updateName(String id, String name) async { Future<Person?> updateName(String id, String name) async {
try { try {
return await _apiService.peopleApi.updatePerson( return await _personApiRepository.update(id, name: name);
id,
PersonUpdateDto(
name: name,
),
);
} catch (error, stack) { } catch (error, stack) {
_log.severe("Error while updating person name", error, stack); _log.severe("Error while updating person name", error, stack);
} }

Binary file not shown.

View file

@ -1,27 +1,27 @@
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/interfaces/asset.interface.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
final searchServiceProvider = Provider( final searchServiceProvider = Provider(
(ref) => SearchService( (ref) => SearchService(
ref.watch(apiServiceProvider), ref.watch(apiServiceProvider),
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
), ),
); );
class SearchService { class SearchService {
final ApiService _apiService; final ApiService _apiService;
final Isar _db; final IAssetRepository _assetRepository;
final _log = Logger("SearchService"); final _log = Logger("SearchService");
SearchService(this._apiService, this._db); SearchService(this._apiService, this._assetRepository);
Future<List<String>?> getSearchSuggestions( Future<List<String>?> getSearchSuggestions(
SearchSuggestionType type, { SearchSuggestionType type, {
@ -103,7 +103,7 @@ class SearchService {
return null; return null;
} }
return _db.assets return _assetRepository
.getAllByRemoteId(response.assets.items.map((e) => e.id)); .getAllByRemoteId(response.assets.items.map((e) => e.id));
} catch (error, stackTrace) { } catch (error, stackTrace) {
_log.severe("Failed to search for assets", error, stackTrace); _log.severe("Failed to search for assets", error, stackTrace);

View file

@ -1,17 +1,17 @@
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/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class StackService { class StackService {
StackService(this._api, this._db); StackService(this._api, this._assetRepository);
final ApiService _api; final ApiService _api;
final Isar _db; final IAssetRepository _assetRepository;
Future<StackResponseDto?> getStack(String stackId) async { Future<StackResponseDto?> getStack(String stackId) async {
try { try {
@ -61,10 +61,7 @@ class StackService {
removeAssets.add(asset); removeAssets.add(asset);
} }
await _assetRepository.updateAll(removeAssets);
_db.writeTxn(() async {
await _db.assets.putAll(removeAssets);
});
} catch (error) { } catch (error) {
debugPrint("Error while deleting stack: $error"); debugPrint("Error while deleting stack: $error");
} }
@ -74,6 +71,6 @@ class StackService {
final stackServiceProvider = Provider( final stackServiceProvider = Provider(
(ref) => StackService( (ref) => StackService(
ref.watch(apiServiceProvider), ref.watch(apiServiceProvider),
ref.watch(dbProvider), ref.watch(assetRepositoryProvider),
), ),
); );

View file

@ -1,68 +1,48 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/services/partner.service.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/interfaces/user.interface.dart';
import 'package:immich_mobile/interfaces/user_api.interface.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:immich_mobile/repositories/user_api.repository.dart';
import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/sync.service.dart';
import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/diff.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
final userServiceProvider = Provider( final userServiceProvider = Provider(
(ref) => UserService( (ref) => UserService(
ref.watch(apiServiceProvider), ref.watch(partnerApiRepositoryProvider),
ref.watch(dbProvider), ref.watch(userApiRepositoryProvider),
ref.watch(userRepositoryProvider),
ref.watch(syncServiceProvider), ref.watch(syncServiceProvider),
ref.watch(partnerServiceProvider),
), ),
); );
class UserService { class UserService {
final ApiService _apiService; final IPartnerApiRepository _partnerApiRepository;
final Isar _db; final IUserApiRepository _userApiRepository;
final IUserRepository _userRepository;
final SyncService _syncService; final SyncService _syncService;
final PartnerService _partnerService;
final Logger _log = Logger("UserService"); final Logger _log = Logger("UserService");
UserService( UserService(
this._apiService, this._partnerApiRepository,
this._db, this._userApiRepository,
this._userRepository,
this._syncService, this._syncService,
this._partnerService,
); );
Future<List<User>?> _getAllUsers() async { Future<List<User>> getUsers({bool self = false}) =>
try { _userRepository.getAll(self: self);
final dto = await _apiService.usersApi.searchUsers();
return dto?.map(User.fromSimpleUserDto).toList();
} catch (e) {
_log.warning("Failed get all users", e);
return null;
}
}
Future<List<User>> getUsersInDb({bool self = false}) async { Future<({String profileImagePath})?> uploadProfileImage(XFile image) async {
if (self) {
return _db.users.where().findAll();
}
final int userId = Store.get(StoreKey.currentUser).isarId;
return _db.users.where().isarIdNotEqualTo(userId).findAll();
}
Future<CreateProfileImageResponseDto?> uploadProfileImage(XFile image) async {
try { try {
return await _apiService.usersApi.createProfileImage( return await _userApiRepository.createProfileImage(
MultipartFile.fromBytes( name: image.name,
'file', data: await image.readAsBytes(),
await image.readAsBytes(),
filename: image.name,
),
); );
} catch (e) { } catch (e) {
_log.warning("Failed to upload profile image", e); _log.warning("Failed to upload profile image", e);
@ -71,13 +51,19 @@ class UserService {
} }
Future<List<User>?> getUsersFromServer() async { Future<List<User>?> getUsersFromServer() async {
final List<User>? users = await _getAllUsers(); List<User>? users;
final List<User>? sharedBy = try {
await _partnerService.getPartners(PartnerDirection.by); users = await _userApiRepository.getAll();
final List<User>? sharedWith = } catch (e) {
await _partnerService.getPartners(PartnerDirection.with_); _log.warning("Failed to fetch users", e);
users = null;
}
final List<User> sharedBy =
await _partnerApiRepository.getAll(Direction.sharedByMe);
final List<User> sharedWith =
await _partnerApiRepository.getAll(Direction.sharedWithMe);
if (users == null || sharedBy == null || sharedWith == null) { if (users == null) {
_log.warning("Failed to refresh users"); _log.warning("Failed to refresh users");
return null; return null;
} }

View file

@ -1,7 +1,7 @@
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
String getThumbnailUrl( String getThumbnailUrl(
@ -61,7 +61,7 @@ String getOriginalUrlForRemoteId(final String id) {
String getImageCacheKey(final Asset asset) { String getImageCacheKey(final Asset asset) {
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = asset.id == Isar.autoIncrement; final isFromDto = asset.id == noDbId;
return '${isFromDto ? asset.remoteId : asset.id}_fullStage'; return '${isFromDto ? asset.remoteId : asset.id}_fullStage';
} }

View file

@ -1,11 +1,11 @@
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/constants/constants.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
import 'package:immich_mobile/utils/storage_indicator.dart'; import 'package:immich_mobile/utils/storage_indicator.dart';
import 'package:isar/isar.dart';
class ThumbnailImage extends ConsumerWidget { class ThumbnailImage extends ConsumerWidget {
/// The asset to show the thumbnail image for /// The asset to show the thumbnail image for
@ -46,7 +46,7 @@ class ThumbnailImage extends ConsumerWidget {
? context.primaryColor.darken(amount: 0.6) ? context.primaryColor.darken(amount: 0.6)
: context.primaryColor.lighten(amount: 0.8); : context.primaryColor.lighten(amount: 0.8);
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = asset.id == Isar.autoIncrement; final isFromDto = asset.id == noDbId;
Widget buildSelectionIcon(Asset asset) { Widget buildSelectionIcon(Asset asset) {
if (isSelected) { if (isSelected) {

View file

@ -3,23 +3,23 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/interfaces/person_api.interface.dart';
import 'package:immich_mobile/providers/search/people.provider.dart'; import 'package:immich_mobile/providers/search/people.provider.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.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 PeoplePicker extends HookConsumerWidget { class PeoplePicker extends HookConsumerWidget {
const PeoplePicker({super.key, required this.onSelect, this.filter}); const PeoplePicker({super.key, required this.onSelect, this.filter});
final Function(Set<PersonResponseDto>) onSelect; final Function(Set<Person>) onSelect;
final Set<PersonResponseDto>? filter; final Set<Person>? filter;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
var imageSize = 45.0; var imageSize = 45.0;
final people = ref.watch(getAllPeopleProvider); final people = ref.watch(getAllPeopleProvider);
final headers = ApiService.getRequestHeaders(); final headers = ApiService.getRequestHeaders();
final selectedPeople = useState<Set<PersonResponseDto>>(filter ?? {}); final selectedPeople = useState<Set<Person>>(filter ?? {});
return people.widgetWhen( return people.widgetWhen(
onData: (people) { onData: (people) {