mirror of
https://github.com/immich-app/immich.git
synced 2025-04-23 13:09:00 +02:00
refactor: user entity (#16655)
* refactor: user entity * fix: add users to album & user profile url * chore: rebase fixes * generate files * fix(mobile): timeline not reset on login * fix: test stub * refactor: rename user model (#16813) * refactor: rename user model * simplify import --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com> * chore: generate files * fix: use getAllAccessible instead of getAll --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
a75718ce99
commit
d1c8fe5303
@ -67,7 +67,7 @@ custom_lint:
|
|||||||
- lib/entities/*.entity.dart
|
- lib/entities/*.entity.dart
|
||||||
- lib/repositories/{album,asset,backup,database,etag,exif_info,user,timeline,partner}.repository.dart
|
- lib/repositories/{album,asset,backup,database,etag,exif_info,user,timeline,partner}.repository.dart
|
||||||
- lib/infrastructure/entities/*.entity.dart
|
- lib/infrastructure/entities/*.entity.dart
|
||||||
- lib/infrastructure/repositories/{store,db,log,exif}.repository.dart
|
- lib/infrastructure/repositories/*.repository.dart
|
||||||
- lib/providers/infrastructure/db.provider.dart
|
- lib/providers/infrastructure/db.provider.dart
|
||||||
# acceptable exceptions for the time being (until Isar is fully replaced)
|
# acceptable exceptions for the time being (until Isar is fully replaced)
|
||||||
- lib/providers/app_life_cycle.provider.dart
|
- lib/providers/app_life_cycle.provider.dart
|
||||||
@ -93,6 +93,7 @@ custom_lint:
|
|||||||
- lib/infrastructure/utils/*.converter.dart
|
- lib/infrastructure/utils/*.converter.dart
|
||||||
# acceptable exceptions for the time being
|
# acceptable exceptions for the time being
|
||||||
- lib/entities/{album,asset,exif_info,user}.entity.dart # to convert DTOs to entities
|
- lib/entities/{album,asset,exif_info,user}.entity.dart # to convert DTOs to entities
|
||||||
|
- lib/infrastructure/utils/*.converter.dart
|
||||||
- lib/utils/{image_url_builder,openapi_patching}.dart # utils are fine
|
- lib/utils/{image_url_builder,openapi_patching}.dart # utils are fine
|
||||||
- test/modules/utils/openapi_patching_test.dart # filename is self-explanatory...
|
- test/modules/utils/openapi_patching_test.dart # filename is self-explanatory...
|
||||||
- lib/domain/services/sync_stream.service.dart # Making sure to comply with the type from database
|
- lib/domain/services/sync_stream.service.dart # Making sure to comply with the type from database
|
||||||
|
24
mobile/lib/domain/interfaces/user.interface.dart
Normal file
24
mobile/lib/domain/interfaces/user.interface.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
|
abstract interface class IUserRepository implements IDatabaseRepository {
|
||||||
|
Future<bool> insert(UserDto user);
|
||||||
|
|
||||||
|
Future<UserDto?> get(int id);
|
||||||
|
|
||||||
|
Future<UserDto?> getByUserId(String id);
|
||||||
|
|
||||||
|
Future<List<UserDto?>> getByUserIds(List<String> ids);
|
||||||
|
|
||||||
|
Future<List<UserDto>> getAll({SortUserBy? sortBy});
|
||||||
|
|
||||||
|
Future<bool> updateAll(List<UserDto> users);
|
||||||
|
|
||||||
|
Future<UserDto> update(UserDto user);
|
||||||
|
|
||||||
|
Future<void> delete(List<int> ids);
|
||||||
|
|
||||||
|
Future<void> deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SortUserBy { id }
|
@ -1,11 +1,11 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
/// Key for each possible value in the `Store`.
|
/// Key for each possible value in the `Store`.
|
||||||
/// Defines the data type for each value
|
/// Defines the data type for each value
|
||||||
enum StoreKey<T> {
|
enum StoreKey<T> {
|
||||||
version<int>._(0),
|
version<int>._(0),
|
||||||
assetETag<String>._(1),
|
assetETag<String>._(1),
|
||||||
currentUser<User>._(2),
|
currentUser<UserDto>._(2),
|
||||||
deviceIdHash<int>._(3),
|
deviceIdHash<int>._(3),
|
||||||
deviceId<String>._(4),
|
deviceId<String>._(4),
|
||||||
backupFailedSince<DateTime>._(5),
|
backupFailedSince<DateTime>._(5),
|
||||||
|
157
mobile/lib/domain/models/user.model.dart
Normal file
157
mobile/lib/domain/models/user.model.dart
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
|
|
||||||
|
enum AvatarColor {
|
||||||
|
// do not change this order or reuse indices for other purposes, adding is OK
|
||||||
|
primary,
|
||||||
|
pink,
|
||||||
|
red,
|
||||||
|
yellow,
|
||||||
|
blue,
|
||||||
|
green,
|
||||||
|
purple,
|
||||||
|
orange,
|
||||||
|
gray,
|
||||||
|
amber;
|
||||||
|
|
||||||
|
Color toColor({bool isDarkTheme = false}) => switch (this) {
|
||||||
|
AvatarColor.primary =>
|
||||||
|
isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
||||||
|
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
|
||||||
|
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
|
||||||
|
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
|
||||||
|
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
|
||||||
|
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
|
||||||
|
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
|
||||||
|
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
|
||||||
|
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
|
||||||
|
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Rename to User once Isar is removed
|
||||||
|
class UserDto {
|
||||||
|
final String uid;
|
||||||
|
final String email;
|
||||||
|
final String name;
|
||||||
|
final bool isAdmin;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
|
||||||
|
final String? profileImagePath;
|
||||||
|
final AvatarColor avatarColor;
|
||||||
|
|
||||||
|
final bool memoryEnabled;
|
||||||
|
final bool inTimeline;
|
||||||
|
|
||||||
|
final bool isPartnerSharedBy;
|
||||||
|
final bool isPartnerSharedWith;
|
||||||
|
|
||||||
|
final int quotaUsageInBytes;
|
||||||
|
final int quotaSizeInBytes;
|
||||||
|
|
||||||
|
int get id => fastHash(uid);
|
||||||
|
bool get hasQuota => quotaSizeInBytes > 0;
|
||||||
|
|
||||||
|
const UserDto({
|
||||||
|
required this.uid,
|
||||||
|
required this.email,
|
||||||
|
required this.name,
|
||||||
|
required this.isAdmin,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.profileImagePath,
|
||||||
|
this.avatarColor = AvatarColor.primary,
|
||||||
|
this.memoryEnabled = true,
|
||||||
|
this.inTimeline = false,
|
||||||
|
this.isPartnerSharedBy = false,
|
||||||
|
this.isPartnerSharedWith = false,
|
||||||
|
this.quotaUsageInBytes = 0,
|
||||||
|
this.quotaSizeInBytes = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''User: {
|
||||||
|
id: $id,
|
||||||
|
uid: $uid,
|
||||||
|
email: $email,
|
||||||
|
name: $name,
|
||||||
|
isAdmin: $isAdmin,
|
||||||
|
updatedAt: $updatedAt,
|
||||||
|
profileImagePath: ${profileImagePath ?? '<NA>'},
|
||||||
|
avatarColor: $avatarColor,
|
||||||
|
memoryEnabled: $memoryEnabled,
|
||||||
|
inTimeline: $inTimeline,
|
||||||
|
isPartnerSharedBy: $isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: $isPartnerSharedWith,
|
||||||
|
quotaUsageInBytes: $quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: $quotaSizeInBytes,
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDto copyWith({
|
||||||
|
String? uid,
|
||||||
|
String? email,
|
||||||
|
String? name,
|
||||||
|
bool? isAdmin,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
String? profileImagePath,
|
||||||
|
AvatarColor? avatarColor,
|
||||||
|
bool? memoryEnabled,
|
||||||
|
bool? inTimeline,
|
||||||
|
bool? isPartnerSharedBy,
|
||||||
|
bool? isPartnerSharedWith,
|
||||||
|
int? quotaUsageInBytes,
|
||||||
|
int? quotaSizeInBytes,
|
||||||
|
}) =>
|
||||||
|
UserDto(
|
||||||
|
uid: uid ?? this.uid,
|
||||||
|
email: email ?? this.email,
|
||||||
|
name: name ?? this.name,
|
||||||
|
isAdmin: isAdmin ?? this.isAdmin,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
profileImagePath: profileImagePath ?? this.profileImagePath,
|
||||||
|
avatarColor: avatarColor ?? this.avatarColor,
|
||||||
|
memoryEnabled: memoryEnabled ?? this.memoryEnabled,
|
||||||
|
inTimeline: inTimeline ?? this.inTimeline,
|
||||||
|
isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith,
|
||||||
|
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant UserDto other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.uid == uid &&
|
||||||
|
other.updatedAt.isAtSameMomentAs(updatedAt) &&
|
||||||
|
other.avatarColor == avatarColor &&
|
||||||
|
other.email == email &&
|
||||||
|
other.name == name &&
|
||||||
|
other.isPartnerSharedBy == isPartnerSharedBy &&
|
||||||
|
other.isPartnerSharedWith == isPartnerSharedWith &&
|
||||||
|
other.profileImagePath == profileImagePath &&
|
||||||
|
other.isAdmin == isAdmin &&
|
||||||
|
other.memoryEnabled == memoryEnabled &&
|
||||||
|
other.inTimeline == inTimeline &&
|
||||||
|
other.quotaUsageInBytes == quotaUsageInBytes &&
|
||||||
|
other.quotaSizeInBytes == quotaSizeInBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
uid.hashCode ^
|
||||||
|
name.hashCode ^
|
||||||
|
email.hashCode ^
|
||||||
|
updatedAt.hashCode ^
|
||||||
|
isAdmin.hashCode ^
|
||||||
|
profileImagePath.hashCode ^
|
||||||
|
avatarColor.hashCode ^
|
||||||
|
memoryEnabled.hashCode ^
|
||||||
|
inTimeline.hashCode ^
|
||||||
|
isPartnerSharedBy.hashCode ^
|
||||||
|
isPartnerSharedWith.hashCode ^
|
||||||
|
quotaUsageInBytes.hashCode ^
|
||||||
|
quotaSizeInBytes.hashCode;
|
||||||
|
}
|
@ -74,7 +74,7 @@ class StoreService {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asynchronously stores the value in the DB and synchronously in the cache
|
/// Asynchronously stores the value in the Store
|
||||||
Future<void> put<U extends StoreKey<T>, T>(U key, T value) async {
|
Future<void> put<U extends StoreKey<T>, T>(U key, T value) async {
|
||||||
if (_cache[key.id] == value) return;
|
if (_cache[key.id] == value) return;
|
||||||
await _storeRepository.insert(key, value);
|
await _storeRepository.insert(key, value);
|
||||||
@ -84,7 +84,7 @@ class StoreService {
|
|||||||
/// Watches a specific key for changes
|
/// Watches a specific key for changes
|
||||||
Stream<T?> watch<T>(StoreKey<T> key) => _storeRepository.watch(key);
|
Stream<T?> watch<T>(StoreKey<T> key) => _storeRepository.watch(key);
|
||||||
|
|
||||||
/// Removes the value asynchronously from the DB and synchronously from the cache
|
/// Removes the value asynchronously from the Store
|
||||||
Future<void> delete<T>(StoreKey<T> key) async {
|
Future<void> delete<T>(StoreKey<T> key) async {
|
||||||
await _storeRepository.delete(key);
|
await _storeRepository.delete(key);
|
||||||
_cache.remove(key.id);
|
_cache.remove(key.id);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/utils/datetime_comparison.dart';
|
import 'package:immich_mobile/utils/datetime_comparison.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
|
||||||
import 'package:immich_mobile/utils/hash.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
|
|
||||||
part 'user.entity.g.dart';
|
|
||||||
|
|
||||||
@Collection(inheritance: false)
|
|
||||||
class User {
|
|
||||||
User({
|
|
||||||
required this.id,
|
|
||||||
required this.updatedAt,
|
|
||||||
required this.email,
|
|
||||||
required this.name,
|
|
||||||
required this.isAdmin,
|
|
||||||
this.isPartnerSharedBy = false,
|
|
||||||
this.isPartnerSharedWith = false,
|
|
||||||
this.profileImagePath = '',
|
|
||||||
this.avatarColor = AvatarColorEnum.primary,
|
|
||||||
this.memoryEnabled = true,
|
|
||||||
this.inTimeline = false,
|
|
||||||
this.quotaUsageInBytes = 0,
|
|
||||||
this.quotaSizeInBytes = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
Id get isarId => fastHash(id);
|
|
||||||
|
|
||||||
User.fromUserDto(
|
|
||||||
UserAdminResponseDto dto,
|
|
||||||
UserPreferencesResponseDto? preferences,
|
|
||||||
) : id = dto.id,
|
|
||||||
updatedAt = dto.updatedAt,
|
|
||||||
email = dto.email,
|
|
||||||
name = dto.name,
|
|
||||||
isPartnerSharedBy = false,
|
|
||||||
isPartnerSharedWith = false,
|
|
||||||
profileImagePath = dto.profileImagePath,
|
|
||||||
isAdmin = dto.isAdmin,
|
|
||||||
memoryEnabled = preferences?.memories.enabled ?? false,
|
|
||||||
avatarColor = dto.avatarColor.toAvatarColor(),
|
|
||||||
inTimeline = false,
|
|
||||||
quotaUsageInBytes = dto.quotaUsageInBytes ?? 0,
|
|
||||||
quotaSizeInBytes = dto.quotaSizeInBytes ?? 0;
|
|
||||||
|
|
||||||
User.fromPartnerDto(PartnerResponseDto dto)
|
|
||||||
: id = dto.id,
|
|
||||||
updatedAt = DateTime.now(),
|
|
||||||
email = dto.email,
|
|
||||||
name = dto.name,
|
|
||||||
isPartnerSharedBy = false,
|
|
||||||
isPartnerSharedWith = false,
|
|
||||||
profileImagePath = dto.profileImagePath,
|
|
||||||
isAdmin = false,
|
|
||||||
memoryEnabled = false,
|
|
||||||
avatarColor = dto.avatarColor.toAvatarColor(),
|
|
||||||
inTimeline = dto.inTimeline ?? false,
|
|
||||||
quotaUsageInBytes = 0,
|
|
||||||
quotaSizeInBytes = 0;
|
|
||||||
|
|
||||||
/// Base user dto used where the complete user object is not required
|
|
||||||
User.fromSimpleUserDto(UserResponseDto dto)
|
|
||||||
: id = dto.id,
|
|
||||||
email = dto.email,
|
|
||||||
name = dto.name,
|
|
||||||
profileImagePath = dto.profileImagePath,
|
|
||||||
avatarColor = dto.avatarColor.toAvatarColor(),
|
|
||||||
// Fill the remaining fields with placeholders
|
|
||||||
isAdmin = false,
|
|
||||||
inTimeline = false,
|
|
||||||
memoryEnabled = false,
|
|
||||||
isPartnerSharedBy = false,
|
|
||||||
isPartnerSharedWith = false,
|
|
||||||
updatedAt = DateTime.now(),
|
|
||||||
quotaUsageInBytes = 0,
|
|
||||||
quotaSizeInBytes = 0;
|
|
||||||
|
|
||||||
@Index(unique: true, replace: false, type: IndexType.hash)
|
|
||||||
String id;
|
|
||||||
DateTime updatedAt;
|
|
||||||
String email;
|
|
||||||
String name;
|
|
||||||
bool isPartnerSharedBy;
|
|
||||||
bool isPartnerSharedWith;
|
|
||||||
bool isAdmin;
|
|
||||||
String profileImagePath;
|
|
||||||
@Enumerated(EnumType.ordinal)
|
|
||||||
AvatarColorEnum avatarColor;
|
|
||||||
bool memoryEnabled;
|
|
||||||
bool inTimeline;
|
|
||||||
int quotaUsageInBytes;
|
|
||||||
int quotaSizeInBytes;
|
|
||||||
|
|
||||||
bool get hasQuota => quotaSizeInBytes > 0;
|
|
||||||
@Backlink(to: 'owner')
|
|
||||||
final IsarLinks<Album> albums = IsarLinks<Album>();
|
|
||||||
@Backlink(to: 'sharedUsers')
|
|
||||||
final IsarLinks<Album> sharedAlbums = IsarLinks<Album>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(other) {
|
|
||||||
if (other is! User) return false;
|
|
||||||
return id == other.id &&
|
|
||||||
updatedAt.isAtSameMomentAs(other.updatedAt) &&
|
|
||||||
avatarColor == other.avatarColor &&
|
|
||||||
email == other.email &&
|
|
||||||
name == other.name &&
|
|
||||||
isPartnerSharedBy == other.isPartnerSharedBy &&
|
|
||||||
isPartnerSharedWith == other.isPartnerSharedWith &&
|
|
||||||
profileImagePath == other.profileImagePath &&
|
|
||||||
isAdmin == other.isAdmin &&
|
|
||||||
memoryEnabled == other.memoryEnabled &&
|
|
||||||
inTimeline == other.inTimeline &&
|
|
||||||
quotaUsageInBytes == other.quotaUsageInBytes &&
|
|
||||||
quotaSizeInBytes == other.quotaSizeInBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@ignore
|
|
||||||
int get hashCode =>
|
|
||||||
id.hashCode ^
|
|
||||||
updatedAt.hashCode ^
|
|
||||||
email.hashCode ^
|
|
||||||
name.hashCode ^
|
|
||||||
isPartnerSharedBy.hashCode ^
|
|
||||||
isPartnerSharedWith.hashCode ^
|
|
||||||
profileImagePath.hashCode ^
|
|
||||||
avatarColor.hashCode ^
|
|
||||||
isAdmin.hashCode ^
|
|
||||||
memoryEnabled.hashCode ^
|
|
||||||
inTimeline.hashCode ^
|
|
||||||
quotaUsageInBytes.hashCode ^
|
|
||||||
quotaSizeInBytes.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AvatarColorEnum {
|
|
||||||
// do not change this order or reuse indices for other purposes, adding is OK
|
|
||||||
primary,
|
|
||||||
pink,
|
|
||||||
red,
|
|
||||||
yellow,
|
|
||||||
blue,
|
|
||||||
green,
|
|
||||||
purple,
|
|
||||||
orange,
|
|
||||||
gray,
|
|
||||||
amber,
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AvatarColorEnumHelper on UserAvatarColor {
|
|
||||||
AvatarColorEnum toAvatarColor() => switch (this) {
|
|
||||||
UserAvatarColor.primary => AvatarColorEnum.primary,
|
|
||||||
UserAvatarColor.pink => AvatarColorEnum.pink,
|
|
||||||
UserAvatarColor.red => AvatarColorEnum.red,
|
|
||||||
UserAvatarColor.yellow => AvatarColorEnum.yellow,
|
|
||||||
UserAvatarColor.blue => AvatarColorEnum.blue,
|
|
||||||
UserAvatarColor.green => AvatarColorEnum.green,
|
|
||||||
UserAvatarColor.purple => AvatarColorEnum.purple,
|
|
||||||
UserAvatarColor.orange => AvatarColorEnum.orange,
|
|
||||||
UserAvatarColor.gray => AvatarColorEnum.gray,
|
|
||||||
UserAvatarColor.amber => AvatarColorEnum.amber,
|
|
||||||
_ => AvatarColorEnum.primary,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AvatarColorToColorHelper on AvatarColorEnum {
|
|
||||||
Color toColor([bool isDarkTheme = false]) => switch (this) {
|
|
||||||
AvatarColorEnum.primary =>
|
|
||||||
isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
|
|
||||||
AvatarColorEnum.pink => const Color.fromARGB(255, 244, 114, 182),
|
|
||||||
AvatarColorEnum.red => const Color.fromARGB(255, 239, 68, 68),
|
|
||||||
AvatarColorEnum.yellow => const Color.fromARGB(255, 234, 179, 8),
|
|
||||||
AvatarColorEnum.blue => const Color.fromARGB(255, 59, 130, 246),
|
|
||||||
AvatarColorEnum.green => const Color.fromARGB(255, 22, 163, 74),
|
|
||||||
AvatarColorEnum.purple => const Color.fromARGB(255, 147, 51, 234),
|
|
||||||
AvatarColorEnum.orange => const Color.fromARGB(255, 234, 88, 12),
|
|
||||||
AvatarColorEnum.gray => const Color.fromARGB(255, 75, 85, 99),
|
|
||||||
AvatarColorEnum.amber => const Color.fromARGB(255, 217, 119, 6),
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
|
|
||||||
extension ListExtension<E> on List<E> {
|
extension ListExtension<E> on List<E> {
|
||||||
List<E> uniqueConsecutive({
|
List<E> uniqueConsecutive({
|
||||||
@ -58,11 +58,11 @@ extension AssetListExtension on Iterable<Asset> {
|
|||||||
/// Returns the assets that are owned by the user passed to the [owner] param
|
/// Returns the assets that are owned by the user passed to the [owner] param
|
||||||
/// If [owner] is null, an empty list is returned
|
/// If [owner] is null, an empty list is returned
|
||||||
Iterable<Asset> ownedOnly(
|
Iterable<Asset> ownedOnly(
|
||||||
User? owner, {
|
UserDto? owner, {
|
||||||
void Function()? errorCallback,
|
void Function()? errorCallback,
|
||||||
}) {
|
}) {
|
||||||
if (owner == null) return [];
|
if (owner == null) return [];
|
||||||
final userId = owner.isarId;
|
final userId = owner.id;
|
||||||
final bool onlyOwned = every((e) => e.ownerId == userId);
|
final bool onlyOwned = every((e) => e.ownerId == userId);
|
||||||
if (!onlyOwned) {
|
if (!onlyOwned) {
|
||||||
if (errorCallback != null) errorCallback();
|
if (errorCallback != null) errorCallback();
|
||||||
|
73
mobile/lib/infrastructure/entities/user.entity.dart
Normal file
73
mobile/lib/infrastructure/entities/user.entity.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
part 'user.entity.g.dart';
|
||||||
|
|
||||||
|
@Collection(inheritance: false)
|
||||||
|
class User {
|
||||||
|
Id get isarId => fastHash(id);
|
||||||
|
@Index(unique: true, replace: false, type: IndexType.hash)
|
||||||
|
final String id;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final String email;
|
||||||
|
final String name;
|
||||||
|
final bool isPartnerSharedBy;
|
||||||
|
final bool isPartnerSharedWith;
|
||||||
|
final bool isAdmin;
|
||||||
|
final String profileImagePath;
|
||||||
|
@Enumerated(EnumType.ordinal)
|
||||||
|
final AvatarColor avatarColor;
|
||||||
|
final bool memoryEnabled;
|
||||||
|
final bool inTimeline;
|
||||||
|
final int quotaUsageInBytes;
|
||||||
|
final int quotaSizeInBytes;
|
||||||
|
|
||||||
|
const User({
|
||||||
|
required this.id,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.email,
|
||||||
|
required this.name,
|
||||||
|
required this.isAdmin,
|
||||||
|
this.isPartnerSharedBy = false,
|
||||||
|
this.isPartnerSharedWith = false,
|
||||||
|
this.profileImagePath = '',
|
||||||
|
this.avatarColor = AvatarColor.primary,
|
||||||
|
this.memoryEnabled = true,
|
||||||
|
this.inTimeline = false,
|
||||||
|
this.quotaUsageInBytes = 0,
|
||||||
|
this.quotaSizeInBytes = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
static User fromDto(UserDto dto) => User(
|
||||||
|
id: dto.uid,
|
||||||
|
updatedAt: dto.updatedAt,
|
||||||
|
email: dto.email,
|
||||||
|
name: dto.name,
|
||||||
|
isAdmin: dto.isAdmin,
|
||||||
|
isPartnerSharedBy: dto.isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: dto.isPartnerSharedWith,
|
||||||
|
profileImagePath: dto.profileImagePath ?? "",
|
||||||
|
avatarColor: dto.avatarColor,
|
||||||
|
memoryEnabled: dto.memoryEnabled,
|
||||||
|
inTimeline: dto.inTimeline,
|
||||||
|
quotaUsageInBytes: dto.quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: dto.quotaSizeInBytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
UserDto toDto() => UserDto(
|
||||||
|
uid: id,
|
||||||
|
email: email,
|
||||||
|
name: name,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
profileImagePath: profileImagePath.isEmpty ? null : profileImagePath,
|
||||||
|
avatarColor: avatarColor,
|
||||||
|
memoryEnabled: memoryEnabled,
|
||||||
|
inTimeline: inTimeline,
|
||||||
|
isPartnerSharedBy: isPartnerSharedBy,
|
||||||
|
isPartnerSharedWith: isPartnerSharedWith,
|
||||||
|
quotaUsageInBytes: quotaUsageInBytes,
|
||||||
|
quotaSizeInBytes: quotaSizeInBytes,
|
||||||
|
);
|
||||||
|
}
|
@ -28,63 +28,58 @@ const UserSchema = CollectionSchema(
|
|||||||
name: r'email',
|
name: r'email',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'hasQuota': PropertySchema(
|
|
||||||
id: 2,
|
|
||||||
name: r'hasQuota',
|
|
||||||
type: IsarType.bool,
|
|
||||||
),
|
|
||||||
r'id': PropertySchema(
|
r'id': PropertySchema(
|
||||||
id: 3,
|
id: 2,
|
||||||
name: r'id',
|
name: r'id',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'inTimeline': PropertySchema(
|
r'inTimeline': PropertySchema(
|
||||||
id: 4,
|
id: 3,
|
||||||
name: r'inTimeline',
|
name: r'inTimeline',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'isAdmin': PropertySchema(
|
r'isAdmin': PropertySchema(
|
||||||
id: 5,
|
id: 4,
|
||||||
name: r'isAdmin',
|
name: r'isAdmin',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'isPartnerSharedBy': PropertySchema(
|
r'isPartnerSharedBy': PropertySchema(
|
||||||
id: 6,
|
id: 5,
|
||||||
name: r'isPartnerSharedBy',
|
name: r'isPartnerSharedBy',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'isPartnerSharedWith': PropertySchema(
|
r'isPartnerSharedWith': PropertySchema(
|
||||||
id: 7,
|
id: 6,
|
||||||
name: r'isPartnerSharedWith',
|
name: r'isPartnerSharedWith',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'memoryEnabled': PropertySchema(
|
r'memoryEnabled': PropertySchema(
|
||||||
id: 8,
|
id: 7,
|
||||||
name: r'memoryEnabled',
|
name: r'memoryEnabled',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'name': PropertySchema(
|
r'name': PropertySchema(
|
||||||
id: 9,
|
id: 8,
|
||||||
name: r'name',
|
name: r'name',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'profileImagePath': PropertySchema(
|
r'profileImagePath': PropertySchema(
|
||||||
id: 10,
|
id: 9,
|
||||||
name: r'profileImagePath',
|
name: r'profileImagePath',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'quotaSizeInBytes': PropertySchema(
|
r'quotaSizeInBytes': PropertySchema(
|
||||||
id: 11,
|
id: 10,
|
||||||
name: r'quotaSizeInBytes',
|
name: r'quotaSizeInBytes',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'quotaUsageInBytes': PropertySchema(
|
r'quotaUsageInBytes': PropertySchema(
|
||||||
id: 12,
|
id: 11,
|
||||||
name: r'quotaUsageInBytes',
|
name: r'quotaUsageInBytes',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'updatedAt': PropertySchema(
|
r'updatedAt': PropertySchema(
|
||||||
id: 13,
|
id: 12,
|
||||||
name: r'updatedAt',
|
name: r'updatedAt',
|
||||||
type: IsarType.dateTime,
|
type: IsarType.dateTime,
|
||||||
)
|
)
|
||||||
@ -109,22 +104,7 @@ const UserSchema = CollectionSchema(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
links: {
|
links: {},
|
||||||
r'albums': LinkSchema(
|
|
||||||
id: -8764917375410137318,
|
|
||||||
name: r'albums',
|
|
||||||
target: r'Album',
|
|
||||||
single: false,
|
|
||||||
linkName: r'owner',
|
|
||||||
),
|
|
||||||
r'sharedAlbums': LinkSchema(
|
|
||||||
id: -7037628715076287024,
|
|
||||||
name: r'sharedAlbums',
|
|
||||||
target: r'Album',
|
|
||||||
single: false,
|
|
||||||
linkName: r'sharedUsers',
|
|
||||||
)
|
|
||||||
},
|
|
||||||
embeddedSchemas: {},
|
embeddedSchemas: {},
|
||||||
getId: _userGetId,
|
getId: _userGetId,
|
||||||
getLinks: _userGetLinks,
|
getLinks: _userGetLinks,
|
||||||
@ -153,18 +133,17 @@ void _userSerialize(
|
|||||||
) {
|
) {
|
||||||
writer.writeByte(offsets[0], object.avatarColor.index);
|
writer.writeByte(offsets[0], object.avatarColor.index);
|
||||||
writer.writeString(offsets[1], object.email);
|
writer.writeString(offsets[1], object.email);
|
||||||
writer.writeBool(offsets[2], object.hasQuota);
|
writer.writeString(offsets[2], object.id);
|
||||||
writer.writeString(offsets[3], object.id);
|
writer.writeBool(offsets[3], object.inTimeline);
|
||||||
writer.writeBool(offsets[4], object.inTimeline);
|
writer.writeBool(offsets[4], object.isAdmin);
|
||||||
writer.writeBool(offsets[5], object.isAdmin);
|
writer.writeBool(offsets[5], object.isPartnerSharedBy);
|
||||||
writer.writeBool(offsets[6], object.isPartnerSharedBy);
|
writer.writeBool(offsets[6], object.isPartnerSharedWith);
|
||||||
writer.writeBool(offsets[7], object.isPartnerSharedWith);
|
writer.writeBool(offsets[7], object.memoryEnabled);
|
||||||
writer.writeBool(offsets[8], object.memoryEnabled);
|
writer.writeString(offsets[8], object.name);
|
||||||
writer.writeString(offsets[9], object.name);
|
writer.writeString(offsets[9], object.profileImagePath);
|
||||||
writer.writeString(offsets[10], object.profileImagePath);
|
writer.writeLong(offsets[10], object.quotaSizeInBytes);
|
||||||
writer.writeLong(offsets[11], object.quotaSizeInBytes);
|
writer.writeLong(offsets[11], object.quotaUsageInBytes);
|
||||||
writer.writeLong(offsets[12], object.quotaUsageInBytes);
|
writer.writeDateTime(offsets[12], object.updatedAt);
|
||||||
writer.writeDateTime(offsets[13], object.updatedAt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User _userDeserialize(
|
User _userDeserialize(
|
||||||
@ -176,19 +155,19 @@ User _userDeserialize(
|
|||||||
final object = User(
|
final object = User(
|
||||||
avatarColor:
|
avatarColor:
|
||||||
_UseravatarColorValueEnumMap[reader.readByteOrNull(offsets[0])] ??
|
_UseravatarColorValueEnumMap[reader.readByteOrNull(offsets[0])] ??
|
||||||
AvatarColorEnum.primary,
|
AvatarColor.primary,
|
||||||
email: reader.readString(offsets[1]),
|
email: reader.readString(offsets[1]),
|
||||||
id: reader.readString(offsets[3]),
|
id: reader.readString(offsets[2]),
|
||||||
inTimeline: reader.readBoolOrNull(offsets[4]) ?? false,
|
inTimeline: reader.readBoolOrNull(offsets[3]) ?? false,
|
||||||
isAdmin: reader.readBool(offsets[5]),
|
isAdmin: reader.readBool(offsets[4]),
|
||||||
isPartnerSharedBy: reader.readBoolOrNull(offsets[6]) ?? false,
|
isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false,
|
||||||
isPartnerSharedWith: reader.readBoolOrNull(offsets[7]) ?? false,
|
isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false,
|
||||||
memoryEnabled: reader.readBoolOrNull(offsets[8]) ?? true,
|
memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true,
|
||||||
name: reader.readString(offsets[9]),
|
name: reader.readString(offsets[8]),
|
||||||
profileImagePath: reader.readStringOrNull(offsets[10]) ?? '',
|
profileImagePath: reader.readStringOrNull(offsets[9]) ?? '',
|
||||||
quotaSizeInBytes: reader.readLongOrNull(offsets[11]) ?? 0,
|
quotaSizeInBytes: reader.readLongOrNull(offsets[10]) ?? 0,
|
||||||
quotaUsageInBytes: reader.readLongOrNull(offsets[12]) ?? 0,
|
quotaUsageInBytes: reader.readLongOrNull(offsets[11]) ?? 0,
|
||||||
updatedAt: reader.readDateTime(offsets[13]),
|
updatedAt: reader.readDateTime(offsets[12]),
|
||||||
);
|
);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
@ -202,32 +181,30 @@ P _userDeserializeProp<P>(
|
|||||||
switch (propertyId) {
|
switch (propertyId) {
|
||||||
case 0:
|
case 0:
|
||||||
return (_UseravatarColorValueEnumMap[reader.readByteOrNull(offset)] ??
|
return (_UseravatarColorValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||||
AvatarColorEnum.primary) as P;
|
AvatarColor.primary) as P;
|
||||||
case 1:
|
case 1:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 2:
|
case 2:
|
||||||
return (reader.readBool(offset)) as P;
|
|
||||||
case 3:
|
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 4:
|
case 3:
|
||||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||||
case 5:
|
case 4:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
|
case 5:
|
||||||
|
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||||
case 6:
|
case 6:
|
||||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||||
case 7:
|
case 7:
|
||||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
|
||||||
case 8:
|
|
||||||
return (reader.readBoolOrNull(offset) ?? true) as P;
|
return (reader.readBoolOrNull(offset) ?? true) as P;
|
||||||
case 9:
|
case 8:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 10:
|
case 9:
|
||||||
return (reader.readStringOrNull(offset) ?? '') as P;
|
return (reader.readStringOrNull(offset) ?? '') as P;
|
||||||
|
case 10:
|
||||||
|
return (reader.readLongOrNull(offset) ?? 0) as P;
|
||||||
case 11:
|
case 11:
|
||||||
return (reader.readLongOrNull(offset) ?? 0) as P;
|
return (reader.readLongOrNull(offset) ?? 0) as P;
|
||||||
case 12:
|
case 12:
|
||||||
return (reader.readLongOrNull(offset) ?? 0) as P;
|
|
||||||
case 13:
|
|
||||||
return (reader.readDateTime(offset)) as P;
|
return (reader.readDateTime(offset)) as P;
|
||||||
default:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
@ -247,16 +224,16 @@ const _UseravatarColorEnumValueMap = {
|
|||||||
'amber': 9,
|
'amber': 9,
|
||||||
};
|
};
|
||||||
const _UseravatarColorValueEnumMap = {
|
const _UseravatarColorValueEnumMap = {
|
||||||
0: AvatarColorEnum.primary,
|
0: AvatarColor.primary,
|
||||||
1: AvatarColorEnum.pink,
|
1: AvatarColor.pink,
|
||||||
2: AvatarColorEnum.red,
|
2: AvatarColor.red,
|
||||||
3: AvatarColorEnum.yellow,
|
3: AvatarColor.yellow,
|
||||||
4: AvatarColorEnum.blue,
|
4: AvatarColor.blue,
|
||||||
5: AvatarColorEnum.green,
|
5: AvatarColor.green,
|
||||||
6: AvatarColorEnum.purple,
|
6: AvatarColor.purple,
|
||||||
7: AvatarColorEnum.orange,
|
7: AvatarColor.orange,
|
||||||
8: AvatarColorEnum.gray,
|
8: AvatarColor.gray,
|
||||||
9: AvatarColorEnum.amber,
|
9: AvatarColor.amber,
|
||||||
};
|
};
|
||||||
|
|
||||||
Id _userGetId(User object) {
|
Id _userGetId(User object) {
|
||||||
@ -264,14 +241,10 @@ Id _userGetId(User object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<IsarLinkBase<dynamic>> _userGetLinks(User object) {
|
List<IsarLinkBase<dynamic>> _userGetLinks(User object) {
|
||||||
return [object.albums, object.sharedAlbums];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _userAttach(IsarCollection<dynamic> col, Id id, User object) {
|
void _userAttach(IsarCollection<dynamic> col, Id id, User object) {}
|
||||||
object.albums.attach(col, col.isar.collection<Album>(), r'albums', id);
|
|
||||||
object.sharedAlbums
|
|
||||||
.attach(col, col.isar.collection<Album>(), r'sharedAlbums', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UserByIndex on IsarCollection<User> {
|
extension UserByIndex on IsarCollection<User> {
|
||||||
Future<User?> getById(String id) {
|
Future<User?> getById(String id) {
|
||||||
@ -447,7 +420,7 @@ extension UserQueryWhere on QueryBuilder<User, User, QWhereClause> {
|
|||||||
|
|
||||||
extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> avatarColorEqualTo(
|
QueryBuilder<User, User, QAfterFilterCondition> avatarColorEqualTo(
|
||||||
AvatarColorEnum value) {
|
AvatarColor value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
property: r'avatarColor',
|
property: r'avatarColor',
|
||||||
@ -457,7 +430,7 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> avatarColorGreaterThan(
|
QueryBuilder<User, User, QAfterFilterCondition> avatarColorGreaterThan(
|
||||||
AvatarColorEnum value, {
|
AvatarColor value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -470,7 +443,7 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> avatarColorLessThan(
|
QueryBuilder<User, User, QAfterFilterCondition> avatarColorLessThan(
|
||||||
AvatarColorEnum value, {
|
AvatarColor value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -483,8 +456,8 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> avatarColorBetween(
|
QueryBuilder<User, User, QAfterFilterCondition> avatarColorBetween(
|
||||||
AvatarColorEnum lower,
|
AvatarColor lower,
|
||||||
AvatarColorEnum upper, {
|
AvatarColor upper, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
}) {
|
}) {
|
||||||
@ -627,15 +600,6 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> hasQuotaEqualTo(bool value) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
|
||||||
property: r'hasQuota',
|
|
||||||
value: value,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> idEqualTo(
|
QueryBuilder<User, User, QAfterFilterCondition> idEqualTo(
|
||||||
String value, {
|
String value, {
|
||||||
bool caseSensitive = true,
|
bool caseSensitive = true,
|
||||||
@ -1285,118 +1249,7 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
|
|||||||
|
|
||||||
extension UserQueryObject on QueryBuilder<User, User, QFilterCondition> {}
|
extension UserQueryObject on QueryBuilder<User, User, QFilterCondition> {}
|
||||||
|
|
||||||
extension UserQueryLinks on QueryBuilder<User, User, QFilterCondition> {
|
extension UserQueryLinks on QueryBuilder<User, User, QFilterCondition> {}
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> albums(FilterQuery<Album> q) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.link(q, r'albums');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> albumsLengthEqualTo(
|
|
||||||
int length) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'albums', length, true, length, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> albumsIsEmpty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'albums', 0, true, 0, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> albumsIsNotEmpty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'albums', 0, false, 999999, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> albumsLengthLessThan(
|
|
||||||
int length, {
|
|
||||||
bool include = false,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'albums', 0, true, length, include);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> albumsLengthGreaterThan(
|
|
||||||
int length, {
|
|
||||||
bool include = false,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'albums', length, include, 999999, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> albumsLengthBetween(
|
|
||||||
int lower,
|
|
||||||
int upper, {
|
|
||||||
bool includeLower = true,
|
|
||||||
bool includeUpper = true,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(
|
|
||||||
r'albums', lower, includeLower, upper, includeUpper);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> sharedAlbums(
|
|
||||||
FilterQuery<Album> q) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.link(q, r'sharedAlbums');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> sharedAlbumsLengthEqualTo(
|
|
||||||
int length) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'sharedAlbums', length, true, length, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> sharedAlbumsIsEmpty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'sharedAlbums', 0, true, 0, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> sharedAlbumsIsNotEmpty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'sharedAlbums', 0, false, 999999, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> sharedAlbumsLengthLessThan(
|
|
||||||
int length, {
|
|
||||||
bool include = false,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'sharedAlbums', 0, true, length, include);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> sharedAlbumsLengthGreaterThan(
|
|
||||||
int length, {
|
|
||||||
bool include = false,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(r'sharedAlbums', length, include, 999999, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterFilterCondition> sharedAlbumsLengthBetween(
|
|
||||||
int lower,
|
|
||||||
int upper, {
|
|
||||||
bool includeLower = true,
|
|
||||||
bool includeUpper = true,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.linkLength(
|
|
||||||
r'sharedAlbums', lower, includeLower, upper, includeUpper);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UserQuerySortBy on QueryBuilder<User, User, QSortBy> {
|
extension UserQuerySortBy on QueryBuilder<User, User, QSortBy> {
|
||||||
QueryBuilder<User, User, QAfterSortBy> sortByAvatarColor() {
|
QueryBuilder<User, User, QAfterSortBy> sortByAvatarColor() {
|
||||||
@ -1423,18 +1276,6 @@ extension UserQuerySortBy on QueryBuilder<User, User, QSortBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterSortBy> sortByHasQuota() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'hasQuota', Sort.asc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterSortBy> sortByHasQuotaDesc() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'hasQuota', Sort.desc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterSortBy> sortById() {
|
QueryBuilder<User, User, QAfterSortBy> sortById() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'id', Sort.asc);
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
@ -1593,18 +1434,6 @@ extension UserQuerySortThenBy on QueryBuilder<User, User, QSortThenBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterSortBy> thenByHasQuota() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'hasQuota', Sort.asc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterSortBy> thenByHasQuotaDesc() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'hasQuota', Sort.desc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QAfterSortBy> thenById() {
|
QueryBuilder<User, User, QAfterSortBy> thenById() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'id', Sort.asc);
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
@ -1764,12 +1593,6 @@ extension UserQueryWhereDistinct on QueryBuilder<User, User, QDistinct> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, User, QDistinct> distinctByHasQuota() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addDistinctBy(r'hasQuota');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, User, QDistinct> distinctById(
|
QueryBuilder<User, User, QDistinct> distinctById(
|
||||||
{bool caseSensitive = true}) {
|
{bool caseSensitive = true}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -1848,7 +1671,7 @@ extension UserQueryProperty on QueryBuilder<User, User, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, AvatarColorEnum, QQueryOperations> avatarColorProperty() {
|
QueryBuilder<User, AvatarColor, QQueryOperations> avatarColorProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'avatarColor');
|
return query.addPropertyName(r'avatarColor');
|
||||||
});
|
});
|
||||||
@ -1860,12 +1683,6 @@ extension UserQueryProperty on QueryBuilder<User, User, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<User, bool, QQueryOperations> hasQuotaProperty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addPropertyName(r'hasQuota');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<User, String, QQueryOperations> idProperty() {
|
QueryBuilder<User, String, QQueryOperations> idProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'id');
|
return query.addPropertyName(r'id');
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
class IsarStoreRepository extends IsarDatabaseRepository
|
class IsarStoreRepository extends IsarDatabaseRepository
|
||||||
@ -78,7 +78,7 @@ class IsarStoreRepository extends IsarDatabaseRepository
|
|||||||
const (DateTime) => entity.intValue == null
|
const (DateTime) => entity.intValue == null
|
||||||
? null
|
? null
|
||||||
: DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
: DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||||
const (User) => await UserRepository(_db).getByDbId(entity.intValue!),
|
const (UserDto) => await IsarUserRepository(_db).get(entity.intValue!),
|
||||||
_ => null,
|
_ => null,
|
||||||
} as T?;
|
} as T?;
|
||||||
|
|
||||||
@ -88,8 +88,8 @@ class IsarStoreRepository extends IsarDatabaseRepository
|
|||||||
const (String) => (null, value as String),
|
const (String) => (null, value as String),
|
||||||
const (bool) => ((value as bool) ? 1 : 0, null),
|
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||||
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||||
const (User) => (
|
const (UserDto) => (
|
||||||
(await UserRepository(_db).update(value as User)).isarId,
|
(await IsarUserRepository(_db).update(value as UserDto)).id,
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
_ => throw UnsupportedError(
|
_ => throw UnsupportedError(
|
||||||
|
80
mobile/lib/infrastructure/repositories/user.repository.dart
Normal file
80
mobile/lib/infrastructure/repositories/user.repository.dart
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||||
|
as entity;
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
class IsarUserRepository extends IsarDatabaseRepository
|
||||||
|
implements IUserRepository {
|
||||||
|
final Isar _db;
|
||||||
|
const IsarUserRepository(super.db) : _db = db;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(List<int> ids) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.deleteAll(ids);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteAll() async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UserDto?> get(int id) async {
|
||||||
|
return (await _db.users.get(id))?.toDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
|
||||||
|
return (await _db.users
|
||||||
|
.where()
|
||||||
|
.optional(
|
||||||
|
sortBy != null,
|
||||||
|
(query) => switch (sortBy!) {
|
||||||
|
SortUserBy.id => query.sortById(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.findAll())
|
||||||
|
.map((u) => u.toDto())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UserDto?> getByUserId(String id) async {
|
||||||
|
return (await _db.users.getById(id))?.toDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<UserDto?>> getByUserIds(List<String> ids) async {
|
||||||
|
return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> insert(UserDto user) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.put(entity.User.fromDto(user));
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UserDto> update(UserDto user) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.put(entity.User.fromDto(user));
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> updateAll(List<UserDto> users) async {
|
||||||
|
await transaction(() async {
|
||||||
|
await _db.users.putAll(users.map(entity.User.fromDto).toList());
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
66
mobile/lib/infrastructure/utils/user.converter.dart
Normal file
66
mobile/lib/infrastructure/utils/user.converter.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
abstract final class UserConverter {
|
||||||
|
/// Base user dto used where the complete user object is not required
|
||||||
|
static UserDto fromSimpleUserDto(UserResponseDto dto) => UserDto(
|
||||||
|
uid: dto.id,
|
||||||
|
email: dto.email,
|
||||||
|
name: dto.name,
|
||||||
|
isAdmin: false,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
profileImagePath: dto.profileImagePath,
|
||||||
|
avatarColor: dto.avatarColor.toAvatarColor(),
|
||||||
|
);
|
||||||
|
|
||||||
|
static UserDto fromAdminDto(
|
||||||
|
UserAdminResponseDto adminDto, [
|
||||||
|
UserPreferencesResponseDto? preferenceDto,
|
||||||
|
]) =>
|
||||||
|
UserDto(
|
||||||
|
uid: adminDto.id,
|
||||||
|
email: adminDto.email,
|
||||||
|
name: adminDto.name,
|
||||||
|
isAdmin: adminDto.isAdmin,
|
||||||
|
updatedAt: adminDto.updatedAt,
|
||||||
|
profileImagePath: adminDto.profileImagePath,
|
||||||
|
avatarColor: adminDto.avatarColor.toAvatarColor(),
|
||||||
|
memoryEnabled: preferenceDto?.memories.enabled ?? true,
|
||||||
|
inTimeline: false,
|
||||||
|
isPartnerSharedBy: false,
|
||||||
|
isPartnerSharedWith: false,
|
||||||
|
quotaUsageInBytes: adminDto.quotaUsageInBytes ?? 0,
|
||||||
|
quotaSizeInBytes: adminDto.quotaSizeInBytes ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
static UserDto fromPartnerDto(PartnerResponseDto dto) => UserDto(
|
||||||
|
uid: dto.id,
|
||||||
|
email: dto.email,
|
||||||
|
name: dto.name,
|
||||||
|
isAdmin: false,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
profileImagePath: dto.profileImagePath,
|
||||||
|
avatarColor: dto.avatarColor.toAvatarColor(),
|
||||||
|
memoryEnabled: false,
|
||||||
|
inTimeline: dto.inTimeline ?? false,
|
||||||
|
isPartnerSharedBy: false,
|
||||||
|
isPartnerSharedWith: false,
|
||||||
|
quotaUsageInBytes: 0,
|
||||||
|
quotaSizeInBytes: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on UserAvatarColor {
|
||||||
|
AvatarColor toAvatarColor() => switch (this) {
|
||||||
|
UserAvatarColor.red => AvatarColor.red,
|
||||||
|
UserAvatarColor.green => AvatarColor.green,
|
||||||
|
UserAvatarColor.blue => AvatarColor.blue,
|
||||||
|
UserAvatarColor.purple => AvatarColor.purple,
|
||||||
|
UserAvatarColor.orange => AvatarColor.orange,
|
||||||
|
UserAvatarColor.pink => AvatarColor.pink,
|
||||||
|
UserAvatarColor.amber => AvatarColor.amber,
|
||||||
|
UserAvatarColor.yellow => AvatarColor.yellow,
|
||||||
|
UserAvatarColor.gray => AvatarColor.gray,
|
||||||
|
UserAvatarColor.primary || _ => AvatarColor.primary,
|
||||||
|
};
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/user.model.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/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/interfaces/database.interface.dart';
|
import 'package:immich_mobile/interfaces/database.interface.dart';
|
||||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||||
|
|
||||||
@ -31,9 +31,9 @@ abstract interface class IAlbumRepository implements IDatabaseRepository {
|
|||||||
|
|
||||||
Future<int> count({bool? local});
|
Future<int> count({bool? local});
|
||||||
|
|
||||||
Future<void> addUsers(Album album, List<User> users);
|
Future<void> addUsers(Album album, List<UserDto> users);
|
||||||
|
|
||||||
Future<void> removeUsers(Album album, List<User> users);
|
Future<void> removeUsers(Album album, List<UserDto> users);
|
||||||
|
|
||||||
Future<void> addAssets(Album album, List<Asset> assets);
|
Future<void> addAssets(Album album, List<Asset> assets);
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
abstract class IPartnerRepository {
|
abstract class IPartnerRepository {
|
||||||
Future<List<User>> getSharedWith();
|
Future<List<UserDto>> getSharedWith();
|
||||||
Future<List<User>> getSharedBy();
|
Future<List<UserDto>> getSharedBy();
|
||||||
Stream<List<User>> watchSharedWith();
|
Stream<List<UserDto>> watchSharedWith();
|
||||||
Stream<List<User>> watchSharedBy();
|
Stream<List<UserDto>> watchSharedBy();
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
abstract interface class IPartnerApiRepository {
|
abstract interface class IPartnerApiRepository {
|
||||||
Future<List<User>> getAll(Direction direction);
|
Future<List<UserDto>> getAll(Direction direction);
|
||||||
Future<User> create(String id);
|
Future<UserDto> create(String id);
|
||||||
Future<User> update(String id, {required bool inTimeline});
|
Future<UserDto> update(String id, {required bool inTimeline});
|
||||||
Future<void> delete(String id);
|
Future<void> delete(String id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/interfaces/database.interface.dart';
|
|
||||||
|
|
||||||
abstract interface class IUserRepository implements IDatabaseRepository {
|
|
||||||
Future<User?> get(String id);
|
|
||||||
|
|
||||||
Future<User?> getByDbId(int id);
|
|
||||||
|
|
||||||
Future<List<User>> getByIds(List<String> ids);
|
|
||||||
|
|
||||||
Future<List<User>> getAll({bool self = true, UserSort? sortBy});
|
|
||||||
|
|
||||||
/// Returns all users whose assets can be accessed (self+partners)
|
|
||||||
Future<List<User>> getAllAccessible();
|
|
||||||
|
|
||||||
Future<List<User>> upsertAll(List<User> users);
|
|
||||||
|
|
||||||
Future<User> update(User user);
|
|
||||||
|
|
||||||
Future<void> deleteById(List<int> ids);
|
|
||||||
|
|
||||||
Future<User> me();
|
|
||||||
|
|
||||||
Future<void> clearTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UserSort { id }
|
|
@ -1,9 +1,9 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
abstract interface class IUserApiRepository {
|
abstract interface class IUserApiRepository {
|
||||||
Future<List<User>> getAll();
|
Future<List<UserDto>> getAll();
|
||||||
Future<({String profileImagePath})> createProfileImage({
|
Future<({String profileImagePath})> createProfileImage({
|
||||||
required String name,
|
required String name,
|
||||||
required Uint8List data,
|
required Uint8List data,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
enum ActivityType { comment, like }
|
enum ActivityType { comment, like }
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ class Activity {
|
|||||||
final String? comment;
|
final String? comment;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final ActivityType type;
|
final ActivityType type;
|
||||||
final User user;
|
final UserDto user;
|
||||||
|
|
||||||
const Activity({
|
const Activity({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -25,7 +25,7 @@ class Activity {
|
|||||||
String? comment,
|
String? comment,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
ActivityType? type,
|
ActivityType? type,
|
||||||
User? user,
|
UserDto? user,
|
||||||
}) {
|
}) {
|
||||||
return Activity(
|
return Activity(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
@ -3,11 +3,11 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/album.entity.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/providers/album/suggested_shared_users.provider.dart';
|
import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -21,15 +21,15 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final AsyncValue<List<User>> suggestedShareUsers =
|
final AsyncValue<List<UserDto>> suggestedShareUsers =
|
||||||
ref.watch(otherUsersProvider);
|
ref.watch(otherUsersProvider);
|
||||||
final sharedUsersList = useState<Set<User>>({});
|
final sharedUsersList = useState<Set<UserDto>>({});
|
||||||
|
|
||||||
addNewUsersHandler() {
|
addNewUsersHandler() {
|
||||||
context.maybePop(sharedUsersList.value.map((e) => e.id).toList());
|
context.maybePop(sharedUsersList.value.map((e) => e.uid).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTileIcon(User user) {
|
buildTileIcon(UserDto user) {
|
||||||
if (sharedUsersList.value.contains(user)) {
|
if (sharedUsersList.value.contains(user)) {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
backgroundColor: context.primaryColor,
|
backgroundColor: context.primaryColor,
|
||||||
@ -45,7 +45,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUserList(List<User> users) {
|
buildUserList(List<UserDto> users) {
|
||||||
List<Widget> usersChip = [];
|
List<Widget> usersChip = [];
|
||||||
|
|
||||||
for (var user in sharedUsersList.value) {
|
for (var user in sharedUsersList.value) {
|
||||||
@ -151,7 +151,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
onData: (users) {
|
onData: (users) {
|
||||||
for (var sharedUsers in album.sharedUsers) {
|
for (var sharedUsers in album.sharedUsers) {
|
||||||
users.removeWhere(
|
users.removeWhere(
|
||||||
(u) => u.id == sharedUsers.id || u.id == album.ownerId,
|
(u) => u.uid == sharedUsers.id || u.uid == album.ownerId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,14 +4,16 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.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/infrastructure/entities/user.entity.dart'
|
||||||
|
as entity;
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/utils/immich_loading_overlay.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||||
|
|
||||||
@ -26,7 +28,8 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
final sharedUsers = useState(album.sharedUsers.toList());
|
final sharedUsers =
|
||||||
|
useState(album.sharedUsers.map((u) => u.toDto()).toList());
|
||||||
final owner = album.owner.value;
|
final owner = album.owner.value;
|
||||||
final userId = ref.watch(authProvider).userId;
|
final userId = ref.watch(authProvider).userId;
|
||||||
final activityEnabled = useState(album.activityEnabled);
|
final activityEnabled = useState(album.activityEnabled);
|
||||||
@ -64,13 +67,13 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
isProcessing.value = false;
|
isProcessing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeUserFromAlbum(User user) async {
|
void removeUserFromAlbum(UserDto user) async {
|
||||||
isProcessing.value = true;
|
isProcessing.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ref.read(albumProvider.notifier).removeUser(album, user);
|
await ref.read(albumProvider.notifier).removeUser(album, user);
|
||||||
album.sharedUsers.remove(user);
|
album.sharedUsers.remove(entity.User.fromDto(user));
|
||||||
sharedUsers.value = album.sharedUsers.toList();
|
sharedUsers.value = album.sharedUsers.map((u) => u.toDto()).toList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorMessage();
|
showErrorMessage();
|
||||||
}
|
}
|
||||||
@ -79,10 +82,10 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
isProcessing.value = false;
|
isProcessing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleUserClick(User user) {
|
void handleUserClick(UserDto user) {
|
||||||
var actions = [];
|
var actions = [];
|
||||||
|
|
||||||
if (user.id == userId) {
|
if (user.uid == userId) {
|
||||||
actions = [
|
actions = [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.exit_to_app_rounded),
|
leading: const Icon(Icons.exit_to_app_rounded),
|
||||||
@ -123,8 +126,9 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
buildOwnerInfo() {
|
buildOwnerInfo() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading:
|
leading: owner != null
|
||||||
owner != null ? UserCircleAvatar(user: owner) : const SizedBox(),
|
? UserCircleAvatar(user: owner.toDto())
|
||||||
|
: const SizedBox(),
|
||||||
title: Text(
|
title: Text(
|
||||||
album.owner.value?.name ?? "",
|
album.owner.value?.name ?? "",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@ -166,10 +170,10 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
|||||||
color: context.colorScheme.onSurfaceSecondary,
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: userId == user.id || isOwner
|
trailing: userId == user.uid || isOwner
|
||||||
? const Icon(Icons.more_horiz_rounded)
|
? const Icon(Icons.more_horiz_rounded)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
onTap: userId == user.id || isOwner
|
onTap: userId == user.uid || isOwner
|
||||||
? () => handleUserClick(user)
|
? () => handleUserClick(user)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||||
@ -12,7 +12,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final sharedUsers = useRef<List<User>>(const []);
|
final sharedUsers = useRef<List<UserDto>>(const []);
|
||||||
sharedUsers.value = ref.watch(
|
sharedUsers.value = ref.watch(
|
||||||
currentAlbumProvider.select((album) {
|
currentAlbumProvider.select((album) {
|
||||||
if (album == null) {
|
if (album == null) {
|
||||||
@ -23,7 +23,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget {
|
|||||||
return sharedUsers.value;
|
return sharedUsers.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return album.sharedUsers.toList(growable: false);
|
return album.sharedUsers.map((u) => u.toDto()).toList(growable: false);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,14 +3,14 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.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/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
import 'package:immich_mobile/providers/album/album_title.provider.dart';
|
||||||
import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart';
|
import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -21,7 +21,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final sharedUsersList = useState<Set<User>>({});
|
final sharedUsersList = useState<Set<UserDto>>({});
|
||||||
final suggestedShareUsers = ref.watch(otherUsersProvider);
|
final suggestedShareUsers = ref.watch(otherUsersProvider);
|
||||||
|
|
||||||
createSharedAlbum() async {
|
createSharedAlbum() async {
|
||||||
@ -48,7 +48,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTileIcon(User user) {
|
buildTileIcon(UserDto user) {
|
||||||
if (sharedUsersList.value.contains(user)) {
|
if (sharedUsersList.value.contains(user)) {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
backgroundColor: context.primaryColor,
|
backgroundColor: context.primaryColor,
|
||||||
@ -64,7 +64,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUserList(List<User> users) {
|
buildUserList(List<UserDto> users) {
|
||||||
List<Widget> usersChip = [];
|
List<Widget> usersChip = [];
|
||||||
|
|
||||||
for (var user in sharedUsersList.value) {
|
for (var user in sharedUsersList.value) {
|
||||||
|
@ -33,7 +33,7 @@ class AlbumsPage extends HookConsumerWidget {
|
|||||||
final searchController = useTextEditingController();
|
final searchController = useTextEditingController();
|
||||||
final debounceTimer = useRef<Timer?>(null);
|
final debounceTimer = useRef<Timer?>(null);
|
||||||
final filterMode = useState(QuickFilterMode.all);
|
final filterMode = useState(QuickFilterMode.all);
|
||||||
final userId = ref.watch(currentUserProvider)?.id;
|
final userId = ref.watch(currentUserProvider)?.uid;
|
||||||
final searchFocusNode = useFocusNode();
|
final searchFocusNode = useFocusNode();
|
||||||
|
|
||||||
toggleViewMode() {
|
toggleViewMode() {
|
||||||
|
@ -7,12 +7,12 @@ 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/models/activities/activity.model.dart';
|
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||||
import 'package:immich_mobile/providers/activity.provider.dart';
|
import 'package:immich_mobile/providers/activity.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/activities/activity_text_field.dart';
|
|
||||||
import 'package:immich_mobile/widgets/activities/activity_tile.dart';
|
|
||||||
import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
|
|
||||||
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/activities/activity_text_field.dart';
|
||||||
|
import 'package:immich_mobile/widgets/activities/activity_tile.dart';
|
||||||
|
import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class ActivitiesPage extends HookConsumerWidget {
|
class ActivitiesPage extends HookConsumerWidget {
|
||||||
@ -72,7 +72,7 @@ class ActivitiesPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
final activity = data[index];
|
final activity = data[index];
|
||||||
final canDelete = activity.user.id == user?.id ||
|
final canDelete = activity.user.id == user?.id ||
|
||||||
album.ownerId == user?.id;
|
album.ownerId == user?.uid;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(5),
|
padding: const EdgeInsets.all(5),
|
||||||
|
@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
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/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.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/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
@ -163,7 +163,7 @@ class QuickAccessButtons extends ConsumerWidget {
|
|||||||
class PartnerList extends ConsumerWidget {
|
class PartnerList extends ConsumerWidget {
|
||||||
const PartnerList({super.key, required this.partners});
|
const PartnerList({super.key, required this.partners});
|
||||||
|
|
||||||
final List<User> partners;
|
final List<UserDto> partners;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
@ -2,10 +2,10 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
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/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/partner.provider.dart';
|
import 'package:immich_mobile/providers/partner.provider.dart';
|
||||||
import 'package:immich_mobile/services/partner.service.dart';
|
import 'package:immich_mobile/services/partner.service.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
import 'package:immich_mobile/widgets/common/user_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_avatar.dart';
|
||||||
@ -16,7 +16,7 @@ class PartnerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final List<User> partners = ref.watch(partnerSharedByProvider);
|
final List<UserDto> partners = ref.watch(partnerSharedByProvider);
|
||||||
final availableUsers = ref.watch(partnerAvailableProvider);
|
final availableUsers = ref.watch(partnerAvailableProvider);
|
||||||
|
|
||||||
addNewUsersHandler() async {
|
addNewUsersHandler() async {
|
||||||
@ -29,13 +29,13 @@ class PartnerPage extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final selectedUser = await showDialog<User>(
|
final selectedUser = await showDialog<UserDto>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
title: const Text("partner_page_select_partner").tr(),
|
title: const Text("partner_page_select_partner").tr(),
|
||||||
children: [
|
children: [
|
||||||
for (User u in users)
|
for (UserDto u in users)
|
||||||
SimpleDialogOption(
|
SimpleDialogOption(
|
||||||
onPressed: () => context.pop(u),
|
onPressed: () => context.pop(u),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -67,7 +67,7 @@ class PartnerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeleteUser(User u) {
|
onDeleteUser(UserDto u) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
@ -80,7 +80,7 @@ class PartnerPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUserList(List<User> users) {
|
buildUserList(List<UserDto> users) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -2,11 +2,11 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/providers/partner.provider.dart';
|
import 'package:immich_mobile/providers/partner.provider.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/timeline.provider.dart';
|
import 'package:immich_mobile/providers/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
@ -15,7 +15,7 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
|||||||
class PartnerDetailPage extends HookConsumerWidget {
|
class PartnerDetailPage extends HookConsumerWidget {
|
||||||
const PartnerDetailPage({super.key, required this.partner});
|
const PartnerDetailPage({super.key, required this.partner});
|
||||||
|
|
||||||
final User partner;
|
final UserDto partner;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -111,7 +111,7 @@ class PartnerDetailPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
renderListProvider: singleUserTimelineProvider(partner.isarId),
|
renderListProvider: singleUserTimelineProvider(partner.id),
|
||||||
onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(),
|
onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(),
|
||||||
deleteEnabled: false,
|
deleteEnabled: false,
|
||||||
favoriteEnabled: false,
|
favoriteEnabled: false,
|
||||||
|
@ -7,16 +7,16 @@ 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/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/multiselect.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/timeline.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/memories/memory_lane.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
|
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||||
|
import 'package:immich_mobile/widgets/memories/memory_lane.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class PhotosPage extends HookConsumerWidget {
|
class PhotosPage extends HookConsumerWidget {
|
||||||
@ -110,7 +110,7 @@ class PhotosPage extends HookConsumerWidget {
|
|||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
renderListProvider: timelineUsers.length > 1
|
renderListProvider: timelineUsers.length > 1
|
||||||
? multiUsersTimelineProvider(timelineUsers)
|
? multiUsersTimelineProvider(timelineUsers)
|
||||||
: singleUserTimelineProvider(currentUser?.isarId),
|
: singleUserTimelineProvider(currentUser?.id),
|
||||||
buildLoadingIndicator: buildLoadingIndicator,
|
buildLoadingIndicator: buildLoadingIndicator,
|
||||||
onRefresh: refreshAssets,
|
onRefresh: refreshAssets,
|
||||||
stackEnabled: true,
|
stackEnabled: true,
|
||||||
|
@ -2,11 +2,11 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||||
import 'package:immich_mobile/services/album.service.dart';
|
import 'package:immich_mobile/services/album.service.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
|
||||||
|
|
||||||
final isRefreshingRemoteAlbumProvider = StateProvider<bool>((ref) => false);
|
final isRefreshingRemoteAlbumProvider = StateProvider<bool>((ref) => false);
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ class AlbumNotifier extends StateNotifier<List<Album>> {
|
|||||||
await albumService.addUsers(album, userIds);
|
await albumService.addUsers(album, userIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> removeUser(Album album, User user) async {
|
Future<bool> removeUser(Album album, UserDto user) async {
|
||||||
final isRemoved = await albumService.removeUser(album, user);
|
final isRemoved = await albumService.removeUser(album, user);
|
||||||
|
|
||||||
if (isRemoved && album.sharedUsers.isEmpty) {
|
if (isRemoved && album.sharedUsers.isEmpty) {
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
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/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/user.service.dart';
|
import 'package:immich_mobile/services/user.service.dart';
|
||||||
|
|
||||||
final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) {
|
final otherUsersProvider =
|
||||||
|
FutureProvider.autoDispose<List<UserDto>>((ref) async {
|
||||||
UserService userService = ref.watch(userServiceProvider);
|
UserService userService = ref.watch(userServiceProvider);
|
||||||
|
final currentUser = ref.watch(currentUserProvider);
|
||||||
|
|
||||||
return userService.getUsers();
|
final allUsers = await userService.getAll();
|
||||||
|
allUsers.removeWhere((u) => currentUser?.id == u.id);
|
||||||
|
return allUsers;
|
||||||
});
|
});
|
||||||
|
@ -2,8 +2,9 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter_udid/flutter_udid.dart';
|
import 'package:flutter_udid/flutter_udid.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||||
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
||||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
@ -105,7 +106,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
String deviceId =
|
String deviceId =
|
||||||
Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
||||||
|
|
||||||
User? user = Store.tryGet(StoreKey.currentUser);
|
UserDto? user = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|
||||||
UserAdminResponseDto? userResponse;
|
UserAdminResponseDto? userResponse;
|
||||||
UserPreferencesResponseDto? userPreferences;
|
UserPreferencesResponseDto? userPreferences;
|
||||||
@ -141,18 +142,18 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
|
|
||||||
// If the user information is successfully retrieved, update the store
|
// If the user information is successfully retrieved, update the store
|
||||||
// Due to the flow of the code, this will always happen on first login
|
// Due to the flow of the code, this will always happen on first login
|
||||||
if (userResponse != null) {
|
if (userResponse == null) {
|
||||||
|
_log.severe("Unable to get user information from the server.");
|
||||||
|
} else {
|
||||||
await Store.put(StoreKey.deviceId, deviceId);
|
await Store.put(StoreKey.deviceId, deviceId);
|
||||||
await Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
|
await Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
|
||||||
await Store.put(
|
await Store.put(
|
||||||
StoreKey.currentUser,
|
StoreKey.currentUser,
|
||||||
User.fromUserDto(userResponse, userPreferences),
|
UserConverter.fromAdminDto(userResponse, userPreferences),
|
||||||
);
|
);
|
||||||
await Store.put(StoreKey.accessToken, accessToken);
|
await Store.put(StoreKey.accessToken, accessToken);
|
||||||
|
|
||||||
user = User.fromUserDto(userResponse, userPreferences);
|
user = UserConverter.fromAdminDto(userResponse, userPreferences);
|
||||||
} else {
|
|
||||||
_log.severe("Unable to get user information from the server.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user is null, the login was not successful
|
// If the user is null, the login was not successful
|
||||||
@ -163,7 +164,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
userId: user.id,
|
userId: user.uid,
|
||||||
userEmail: user.email,
|
userEmail: user.email,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
profileImagePath: user.profileImagePath,
|
profileImagePath: user.profileImagePath,
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'store.provider.g.dart';
|
part 'store.provider.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@Riverpod(keepAlive: true)
|
||||||
IStoreRepository storeRepository(Ref ref) =>
|
IStoreRepository storeRepository(Ref ref) =>
|
||||||
IsarStoreRepository(ref.watch(isarProvider));
|
IsarStoreRepository(ref.watch(isarProvider));
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
StoreService storeService(Ref _) => StoreService.I;
|
||||||
|
@ -6,11 +6,11 @@ part of 'store.provider.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$storeRepositoryHash() => r'9f378b96e552151fa14a8c8ce2c30a5f38f436ed';
|
String _$storeRepositoryHash() => r'99d24875d30c5e86b1c6caa352a0026167114e62';
|
||||||
|
|
||||||
/// See also [storeRepository].
|
/// See also [storeRepository].
|
||||||
@ProviderFor(storeRepository)
|
@ProviderFor(storeRepository)
|
||||||
final storeRepositoryProvider = AutoDisposeProvider<IStoreRepository>.internal(
|
final storeRepositoryProvider = Provider<IStoreRepository>.internal(
|
||||||
storeRepository,
|
storeRepository,
|
||||||
name: r'storeRepositoryProvider',
|
name: r'storeRepositoryProvider',
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
@ -22,6 +22,22 @@ final storeRepositoryProvider = AutoDisposeProvider<IStoreRepository>.internal(
|
|||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
typedef StoreRepositoryRef = AutoDisposeProviderRef<IStoreRepository>;
|
typedef StoreRepositoryRef = ProviderRef<IStoreRepository>;
|
||||||
|
String _$storeServiceHash() => r'250e10497c42df360e9e1f9a618d0b19c1b5b0a0';
|
||||||
|
|
||||||
|
/// See also [storeService].
|
||||||
|
@ProviderFor(storeService)
|
||||||
|
final storeServiceProvider = Provider<StoreService>.internal(
|
||||||
|
storeService,
|
||||||
|
name: r'storeServiceProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$storeServiceHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef StoreServiceRef = ProviderRef<StoreService>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
11
mobile/lib/providers/infrastructure/user.provider.dart
Normal file
11
mobile/lib/providers/infrastructure/user.provider.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'user.provider.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
IUserRepository userRepository(Ref ref) =>
|
||||||
|
IsarUserRepository(ref.watch(isarProvider));
|
27
mobile/lib/providers/infrastructure/user.provider.g.dart
generated
Normal file
27
mobile/lib/providers/infrastructure/user.provider.g.dart
generated
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'user.provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$userRepositoryHash() => r'1a2ac726bcc44397dcaecf449084fefd336696d4';
|
||||||
|
|
||||||
|
/// See also [userRepository].
|
||||||
|
@ProviderFor(userRepository)
|
||||||
|
final userRepositoryProvider = Provider<IUserRepository>.internal(
|
||||||
|
userRepository,
|
||||||
|
name: r'userRepositoryProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$userRepositoryHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef UserRepositoryRef = ProviderRef<IUserRepository>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
@ -2,16 +2,16 @@ import 'dart:async';
|
|||||||
|
|
||||||
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:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart';
|
import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart';
|
||||||
import 'package:immich_mobile/services/partner.service.dart';
|
import 'package:immich_mobile/services/partner.service.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
|
|
||||||
class PartnerSharedWithNotifier extends StateNotifier<List<User>> {
|
class PartnerSharedWithNotifier extends StateNotifier<List<UserDto>> {
|
||||||
final PartnerService _partnerService;
|
final PartnerService _partnerService;
|
||||||
late final StreamSubscription<List<User>> streamSub;
|
late final StreamSubscription<List<UserDto>> streamSub;
|
||||||
|
|
||||||
PartnerSharedWithNotifier(this._partnerService) : super([]) {
|
PartnerSharedWithNotifier(this._partnerService) : super([]) {
|
||||||
Function eq = const ListEquality<User>().equals;
|
Function eq = const ListEquality<UserDto>().equals;
|
||||||
_partnerService.getSharedWith().then((partners) {
|
_partnerService.getSharedWith().then((partners) {
|
||||||
if (!eq(state, partners)) {
|
if (!eq(state, partners)) {
|
||||||
state = partners;
|
state = partners;
|
||||||
@ -25,7 +25,7 @@ class PartnerSharedWithNotifier extends StateNotifier<List<User>> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updatePartner(User partner, {required bool inTimeline}) {
|
Future<bool> updatePartner(UserDto partner, {required bool inTimeline}) {
|
||||||
return _partnerService.updatePartner(partner, inTimeline: inTimeline);
|
return _partnerService.updatePartner(partner, inTimeline: inTimeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,18 +39,18 @@ class PartnerSharedWithNotifier extends StateNotifier<List<User>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final partnerSharedWithProvider =
|
final partnerSharedWithProvider =
|
||||||
StateNotifierProvider<PartnerSharedWithNotifier, List<User>>((ref) {
|
StateNotifierProvider<PartnerSharedWithNotifier, List<UserDto>>((ref) {
|
||||||
return PartnerSharedWithNotifier(
|
return PartnerSharedWithNotifier(
|
||||||
ref.watch(partnerServiceProvider),
|
ref.watch(partnerServiceProvider),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
class PartnerSharedByNotifier extends StateNotifier<List<User>> {
|
class PartnerSharedByNotifier extends StateNotifier<List<UserDto>> {
|
||||||
final PartnerService _partnerService;
|
final PartnerService _partnerService;
|
||||||
late final StreamSubscription<List<User>> streamSub;
|
late final StreamSubscription<List<UserDto>> streamSub;
|
||||||
|
|
||||||
PartnerSharedByNotifier(this._partnerService) : super([]) {
|
PartnerSharedByNotifier(this._partnerService) : super([]) {
|
||||||
Function eq = const ListEquality<User>().equals;
|
Function eq = const ListEquality<UserDto>().equals;
|
||||||
_partnerService.getSharedBy().then((partners) {
|
_partnerService.getSharedBy().then((partners) {
|
||||||
if (!eq(state, partners)) {
|
if (!eq(state, partners)) {
|
||||||
state = partners;
|
state = partners;
|
||||||
@ -74,15 +74,15 @@ class PartnerSharedByNotifier extends StateNotifier<List<User>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final partnerSharedByProvider =
|
final partnerSharedByProvider =
|
||||||
StateNotifierProvider<PartnerSharedByNotifier, List<User>>((ref) {
|
StateNotifierProvider<PartnerSharedByNotifier, List<UserDto>>((ref) {
|
||||||
return PartnerSharedByNotifier(ref.watch(partnerServiceProvider));
|
return PartnerSharedByNotifier(ref.watch(partnerServiceProvider));
|
||||||
});
|
});
|
||||||
|
|
||||||
final partnerAvailableProvider =
|
final partnerAvailableProvider =
|
||||||
FutureProvider.autoDispose<List<User>>((ref) async {
|
FutureProvider.autoDispose<List<UserDto>>((ref) async {
|
||||||
final otherUsers = await ref.watch(otherUsersProvider.future);
|
final otherUsers = await ref.watch(otherUsersProvider.future);
|
||||||
final currentPartners = ref.watch(partnerSharedByProvider);
|
final currentPartners = ref.watch(partnerSharedByProvider);
|
||||||
final available = Set<User>.of(otherUsers);
|
final available = Set<UserDto>.of(otherUsers);
|
||||||
available.removeAll(currentPartners);
|
available.removeAll(currentPartners);
|
||||||
return available.toList();
|
return available.toList();
|
||||||
});
|
});
|
||||||
|
@ -2,13 +2,14 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/timeline.service.dart';
|
import 'package:immich_mobile/services/timeline.service.dart';
|
||||||
|
|
||||||
class CurrentUserProvider extends StateNotifier<User?> {
|
class CurrentUserProvider extends StateNotifier<UserDto?> {
|
||||||
CurrentUserProvider(this._apiService) : super(null) {
|
CurrentUserProvider(this._apiService) : super(null) {
|
||||||
state = Store.tryGet(StoreKey.currentUser);
|
state = Store.tryGet(StoreKey.currentUser);
|
||||||
streamSub =
|
streamSub =
|
||||||
@ -16,7 +17,7 @@ class CurrentUserProvider extends StateNotifier<User?> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
late final StreamSubscription<User?> streamSub;
|
late final StreamSubscription<UserDto?> streamSub;
|
||||||
|
|
||||||
refresh() async {
|
refresh() async {
|
||||||
try {
|
try {
|
||||||
@ -25,7 +26,7 @@ class CurrentUserProvider extends StateNotifier<User?> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
await Store.put(
|
await Store.put(
|
||||||
StoreKey.currentUser,
|
StoreKey.currentUser,
|
||||||
User.fromUserDto(user, userPreferences),
|
UserConverter.fromAdminDto(user, userPreferences),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -39,7 +40,7 @@ class CurrentUserProvider extends StateNotifier<User?> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final currentUserProvider =
|
final currentUserProvider =
|
||||||
StateNotifierProvider<CurrentUserProvider, User?>((ref) {
|
StateNotifierProvider<CurrentUserProvider, UserDto?>((ref) {
|
||||||
return CurrentUserProvider(
|
return CurrentUserProvider(
|
||||||
ref.watch(apiServiceProvider),
|
ref.watch(apiServiceProvider),
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
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/infrastructure/utils/user.converter.dart';
|
||||||
import 'package:immich_mobile/interfaces/activity_api.interface.dart';
|
import 'package:immich_mobile/interfaces/activity_api.interface.dart';
|
||||||
import 'package:immich_mobile/models/activities/activity.model.dart';
|
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
@ -60,7 +60,7 @@ class ActivityApiRepository extends ApiRepository
|
|||||||
type: dto.type == ReactionType.comment
|
type: dto.type == ReactionType.comment
|
||||||
? ActivityType.comment
|
? ActivityType.comment
|
||||||
: ActivityType.like,
|
: ActivityType.like,
|
||||||
user: User.fromSimpleUserDto(dto.user),
|
user: UserConverter.fromSimpleUserDto(dto.user),
|
||||||
assetId: dto.assetId,
|
assetId: dto.assetId,
|
||||||
comment: dto.comment,
|
comment: dto.comment,
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.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:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||||
|
as entity;
|
||||||
import 'package:immich_mobile/interfaces/album.interface.dart';
|
import 'package:immich_mobile/interfaces/album.interface.dart';
|
||||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
@ -43,11 +45,11 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
|
|||||||
}
|
}
|
||||||
if (owner == true) {
|
if (owner == true) {
|
||||||
query = query.owner(
|
query = query.owner(
|
||||||
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
|
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id),
|
||||||
);
|
);
|
||||||
} else if (owner == false) {
|
} else if (owner == false) {
|
||||||
query = query.owner(
|
query = query.owner(
|
||||||
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
|
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (remote == true) {
|
if (remote == true) {
|
||||||
@ -100,8 +102,9 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
|
|||||||
Future<Album?> get(int id) => db.albums.get(id);
|
Future<Album?> get(int id) => db.albums.get(id);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removeUsers(Album album, List<User> users) =>
|
Future<void> removeUsers(Album album, List<UserDto> users) => txn(
|
||||||
txn(() => album.sharedUsers.update(unlink: users));
|
() => album.sharedUsers.update(unlink: users.map(entity.User.fromDto)),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addAssets(Album album, List<Asset> assets) =>
|
Future<void> addAssets(Album album, List<Asset> assets) =>
|
||||||
@ -121,8 +124,8 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addUsers(Album album, List<User> users) =>
|
Future<void> addUsers(Album album, List<UserDto> users) =>
|
||||||
txn(() => album.sharedUsers.update(link: users));
|
txn(() => album.sharedUsers.update(link: users.map(entity.User.fromDto)));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteAllLocal() =>
|
Future<void> deleteAllLocal() =>
|
||||||
@ -141,11 +144,11 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
|
|||||||
switch (filterMode) {
|
switch (filterMode) {
|
||||||
case QuickFilterMode.sharedWithMe:
|
case QuickFilterMode.sharedWithMe:
|
||||||
query = query.owner(
|
query = query.owner(
|
||||||
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
|
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id),
|
||||||
);
|
);
|
||||||
case QuickFilterMode.myAlbums:
|
case QuickFilterMode.myAlbums:
|
||||||
query = query.owner(
|
query = query.owner(
|
||||||
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
|
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id),
|
||||||
);
|
);
|
||||||
case QuickFilterMode.all:
|
case QuickFilterMode.all:
|
||||||
break;
|
break;
|
||||||
|
@ -2,7 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.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/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||||
|
as entity;
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||||
import 'package:immich_mobile/interfaces/album_api.interface.dart';
|
import 'package:immich_mobile/interfaces/album_api.interface.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/api.repository.dart';
|
import 'package:immich_mobile/repositories/api.repository.dart';
|
||||||
@ -164,11 +166,12 @@ class AlbumApiRepository extends ApiRepository implements IAlbumApiRepository {
|
|||||||
sortOrder: dto.order == AssetOrder.asc ? SortOrder.asc : SortOrder.desc,
|
sortOrder: dto.order == AssetOrder.asc ? SortOrder.asc : SortOrder.desc,
|
||||||
);
|
);
|
||||||
album.remoteAssetCount = dto.assetCount;
|
album.remoteAssetCount = dto.assetCount;
|
||||||
album.owner.value = User.fromSimpleUserDto(dto.owner);
|
album.owner.value =
|
||||||
|
entity.User.fromDto(UserConverter.fromSimpleUserDto(dto.owner));
|
||||||
album.remoteThumbnailAssetId = dto.albumThumbnailAssetId;
|
album.remoteThumbnailAssetId = dto.albumThumbnailAssetId;
|
||||||
final users = dto.albumUsers
|
final users = dto.albumUsers
|
||||||
.map((albumUser) => User.fromSimpleUserDto(albumUser.user));
|
.map((albumUser) => UserConverter.fromSimpleUserDto(albumUser.user));
|
||||||
album.sharedUsers.addAll(users);
|
album.sharedUsers.addAll(users.map(entity.User.fromDto));
|
||||||
final assets = dto.assets.map(Asset.remote).toList();
|
final assets = dto.assets.map(Asset.remote).toList();
|
||||||
album.assets.addAll(assets);
|
album.assets.addAll(assets);
|
||||||
return album;
|
return album;
|
||||||
|
@ -3,6 +3,7 @@ import 'package:immich_mobile/domain/models/store.model.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:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/interfaces/album_media.interface.dart';
|
import 'package:immich_mobile/interfaces/album_media.interface.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart' hide AssetType;
|
import 'package:photo_manager/photo_manager.dart' hide AssetType;
|
||||||
@ -86,7 +87,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
|
|||||||
shared: false,
|
shared: false,
|
||||||
activityEnabled: false,
|
activityEnabled: false,
|
||||||
);
|
);
|
||||||
album.owner.value = Store.get(StoreKey.currentUser);
|
album.owner.value = User.fromDto(Store.get(StoreKey.currentUser));
|
||||||
album.localId = assetPathEntity.id;
|
album.localId = assetPathEntity.id;
|
||||||
album.isAll = assetPathEntity.isAll;
|
album.isAll = assetPathEntity.isAll;
|
||||||
return album;
|
return album;
|
||||||
|
@ -24,7 +24,7 @@ class AssetMediaRepository implements IAssetMediaRepository {
|
|||||||
final Asset asset = Asset(
|
final Asset asset = Asset(
|
||||||
checksum: "",
|
checksum: "",
|
||||||
localId: local.id,
|
localId: local.id,
|
||||||
ownerId: Store.get(StoreKey.currentUser).isarId,
|
ownerId: Store.get(StoreKey.currentUser).id,
|
||||||
fileCreatedAt: local.createDateTime,
|
fileCreatedAt: local.createDateTime,
|
||||||
fileModifiedAt: local.modifiedDateTime,
|
fileModifiedAt: local.modifiedDateTime,
|
||||||
updatedAt: local.modifiedDateTime,
|
updatedAt: local.modifiedDateTime,
|
||||||
|
@ -6,8 +6,8 @@ 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/etag.entity.dart';
|
import 'package:immich_mobile/entities/etag.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/interfaces/auth.interface.dart';
|
import 'package:immich_mobile/interfaces/auth.interface.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
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/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||||
|
as entity;
|
||||||
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/database.repository.dart';
|
import 'package:immich_mobile/repositories/database.repository.dart';
|
||||||
@ -14,34 +16,40 @@ class PartnerRepository extends DatabaseRepository
|
|||||||
PartnerRepository(super.db);
|
PartnerRepository(super.db);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<User>> getSharedBy() {
|
Future<List<UserDto>> getSharedBy() async {
|
||||||
return db.users
|
return (await db.users
|
||||||
.filter()
|
.filter()
|
||||||
.isPartnerSharedByEqualTo(true)
|
.isPartnerSharedByEqualTo(true)
|
||||||
.sortById()
|
.sortById()
|
||||||
.findAll();
|
.findAll())
|
||||||
|
.map((u) => u.toDto())
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<User>> getSharedWith() {
|
Future<List<UserDto>> getSharedWith() async {
|
||||||
return db.users
|
return (await db.users
|
||||||
.filter()
|
.filter()
|
||||||
.isPartnerSharedWithEqualTo(true)
|
.isPartnerSharedWithEqualTo(true)
|
||||||
.sortById()
|
.sortById()
|
||||||
.findAll();
|
.findAll())
|
||||||
|
.map((u) => u.toDto())
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<User>> watchSharedBy() {
|
Stream<List<UserDto>> watchSharedBy() {
|
||||||
return db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch();
|
return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch())
|
||||||
|
.map((users) => users.map((u) => u.toDto()).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<User>> watchSharedWith() {
|
Stream<List<UserDto>> watchSharedWith() {
|
||||||
return db.users
|
return (db.users
|
||||||
.filter()
|
.filter()
|
||||||
.isPartnerSharedWithEqualTo(true)
|
.isPartnerSharedWithEqualTo(true)
|
||||||
.sortById()
|
.sortById()
|
||||||
.watch();
|
.watch())
|
||||||
|
.map((users) => users.map((u) => u.toDto()).toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/api.repository.dart';
|
import 'package:immich_mobile/repositories/api.repository.dart';
|
||||||
@ -18,7 +19,7 @@ class PartnerApiRepository extends ApiRepository
|
|||||||
PartnerApiRepository(this._api);
|
PartnerApiRepository(this._api);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<User>> getAll(Direction direction) async {
|
Future<List<UserDto>> getAll(Direction direction) async {
|
||||||
final response = await checkNull(
|
final response = await checkNull(
|
||||||
_api.getPartners(
|
_api.getPartners(
|
||||||
direction == Direction.sharedByMe
|
direction == Direction.sharedByMe
|
||||||
@ -26,26 +27,26 @@ class PartnerApiRepository extends ApiRepository
|
|||||||
: PartnerDirection.with_,
|
: PartnerDirection.with_,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return response.map(User.fromPartnerDto).toList();
|
return response.map(UserConverter.fromPartnerDto).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<User> create(String id) async {
|
Future<UserDto> create(String id) async {
|
||||||
final dto = await checkNull(_api.createPartner(id));
|
final dto = await checkNull(_api.createPartner(id));
|
||||||
return User.fromPartnerDto(dto);
|
return UserConverter.fromPartnerDto(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> delete(String id) => _api.removePartner(id);
|
Future<void> delete(String id) => _api.removePartner(id);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<User> update(String id, {required bool inTimeline}) async {
|
Future<UserDto> update(String id, {required bool inTimeline}) async {
|
||||||
final dto = await checkNull(
|
final dto = await checkNull(
|
||||||
_api.updatePartner(
|
_api.updatePartner(
|
||||||
id,
|
id,
|
||||||
UpdatePartnerDto(inTimeline: inTimeline),
|
UpdatePartnerDto(inTimeline: inTimeline),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return User.fromPartnerDto(dto);
|
return UserConverter.fromPartnerDto(dto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.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/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/interfaces/timeline.interface.dart';
|
import 'package:immich_mobile/interfaces/timeline.interface.dart';
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/database.repository.dart';
|
import 'package:immich_mobile/repositories/database.repository.dart';
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
|
||||||
import 'package:immich_mobile/repositories/database.repository.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
|
|
||||||
final userRepositoryProvider =
|
|
||||||
Provider((ref) => UserRepository(ref.watch(dbProvider)));
|
|
||||||
|
|
||||||
class UserRepository extends DatabaseRepository implements IUserRepository {
|
|
||||||
UserRepository(super.db);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> getByIds(List<String> ids) async =>
|
|
||||||
(await db.users.getAllById(ids)).nonNulls.toList();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User?> get(String id) => db.users.getById(id);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> getAll({bool self = true, UserSort? sortBy}) {
|
|
||||||
final baseQuery = db.users.where();
|
|
||||||
final int userId = Store.get(StoreKey.currentUser).isarId;
|
|
||||||
final QueryBuilder<User, User, QAfterWhereClause> afterWhere =
|
|
||||||
self ? baseQuery.noOp() : baseQuery.isarIdNotEqualTo(userId);
|
|
||||||
final QueryBuilder<User, User, QAfterSortBy> query = switch (sortBy) {
|
|
||||||
null => afterWhere.noOp(),
|
|
||||||
UserSort.id => afterWhere.sortById(),
|
|
||||||
};
|
|
||||||
return query.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User> update(User user) async {
|
|
||||||
await txn(() => db.users.put(user));
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User> me() => Future.value(Store.get(StoreKey.currentUser));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deleteById(List<int> ids) => txn(() => db.users.deleteAll(ids));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> upsertAll(List<User> users) async {
|
|
||||||
await txn(() => db.users.putAll(users));
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<User>> getAllAccessible() => db.users
|
|
||||||
.filter()
|
|
||||||
.isPartnerSharedWithEqualTo(true)
|
|
||||||
.or()
|
|
||||||
.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
|
||||||
.findAll();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<User?> getByDbId(int id) async {
|
|
||||||
return await db.users.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> clearTable() async {
|
|
||||||
await txn(() async {
|
|
||||||
await db.users.clear();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,8 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||||
import 'package:immich_mobile/interfaces/user_api.interface.dart';
|
import 'package:immich_mobile/interfaces/user_api.interface.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/api.repository.dart';
|
import 'package:immich_mobile/repositories/api.repository.dart';
|
||||||
@ -20,9 +21,9 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
|
|||||||
UserApiRepository(this._api);
|
UserApiRepository(this._api);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<User>> getAll() async {
|
Future<List<UserDto>> getAll() async {
|
||||||
final dto = await checkNull(_api.searchUsers());
|
final dto = await checkNull(_api.searchUsers());
|
||||||
return dto.map(User.fromSimpleUserDto).toList();
|
return dto.map(UserConverter.fromSimpleUserDto).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -2,11 +2,10 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
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/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.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/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/models/folder/recursive_folder.model.dart';
|
import 'package:immich_mobile/models/folder/recursive_folder.model.dart';
|
||||||
import 'package:immich_mobile/pages/library/folder/folder.page.dart';
|
|
||||||
import 'package:immich_mobile/models/memories/memory.model.dart';
|
import 'package:immich_mobile/models/memories/memory.model.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/models/shared_link/shared_link.model.dart';
|
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||||
@ -37,6 +36,7 @@ import 'package:immich_mobile/pages/editing/edit.page.dart';
|
|||||||
import 'package:immich_mobile/pages/editing/filter.page.dart';
|
import 'package:immich_mobile/pages/editing/filter.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/archive.page.dart';
|
import 'package:immich_mobile/pages/library/archive.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
import 'package:immich_mobile/pages/library/favorite.page.dart';
|
||||||
|
import 'package:immich_mobile/pages/library/folder/folder.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/library.page.dart';
|
import 'package:immich_mobile/pages/library/library.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/local_albums.page.dart';
|
import 'package:immich_mobile/pages/library/local_albums.page.dart';
|
||||||
import 'package:immich_mobile/pages/library/partner/partner.page.dart';
|
import 'package:immich_mobile/pages/library/partner/partner.page.dart';
|
||||||
|
@ -1162,7 +1162,7 @@ class NativeVideoViewerRouteArgs {
|
|||||||
class PartnerDetailRoute extends PageRouteInfo<PartnerDetailRouteArgs> {
|
class PartnerDetailRoute extends PageRouteInfo<PartnerDetailRouteArgs> {
|
||||||
PartnerDetailRoute({
|
PartnerDetailRoute({
|
||||||
Key? key,
|
Key? key,
|
||||||
required User partner,
|
required UserDto partner,
|
||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
PartnerDetailRoute.name,
|
PartnerDetailRoute.name,
|
||||||
@ -1195,7 +1195,7 @@ class PartnerDetailRouteArgs {
|
|||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
final User partner;
|
final UserDto partner;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/memory.provider.dart';
|
import 'package:immich_mobile/providers/memory.provider.dart';
|
||||||
@ -39,7 +39,7 @@ class TabNavigationObserver extends AutoRouterObserver {
|
|||||||
|
|
||||||
await Store.put(
|
await Store.put(
|
||||||
StoreKey.currentUser,
|
StoreKey.currentUser,
|
||||||
User.fromUserDto(userResponseDto, userPreferences),
|
UserConverter.fromAdminDto(userResponseDto, userPreferences),
|
||||||
);
|
);
|
||||||
ref.read(serverInfoProvider.notifier).getServerVersion();
|
ref.read(serverInfoProvider.notifier).getServerVersion();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -7,11 +7,13 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.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/backup_album.entity.dart';
|
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||||
|
as entity;
|
||||||
import 'package:immich_mobile/interfaces/album.interface.dart';
|
import 'package:immich_mobile/interfaces/album.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/album_api.interface.dart';
|
import 'package:immich_mobile/interfaces/album_api.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/album_media.interface.dart';
|
import 'package:immich_mobile/interfaces/album_media.interface.dart';
|
||||||
@ -202,12 +204,12 @@ class AlbumService {
|
|||||||
Future<Album?> createAlbum(
|
Future<Album?> createAlbum(
|
||||||
String albumName,
|
String albumName,
|
||||||
Iterable<Asset> assets, [
|
Iterable<Asset> assets, [
|
||||||
Iterable<User> sharedUsers = const [],
|
Iterable<UserDto> sharedUsers = const [],
|
||||||
]) async {
|
]) async {
|
||||||
final Album album = await _albumApiRepository.create(
|
final Album album = await _albumApiRepository.create(
|
||||||
albumName,
|
albumName,
|
||||||
assetIds: assets.map((asset) => asset.remoteId!),
|
assetIds: assets.map((asset) => asset.remoteId!),
|
||||||
sharedUserIds: sharedUsers.map((user) => user.id),
|
sharedUserIds: sharedUsers.map((user) => user.uid),
|
||||||
);
|
);
|
||||||
await _entityService.fillAlbumWithDatabaseEntities(album);
|
await _entityService.fillAlbumWithDatabaseEntities(album);
|
||||||
return _albumRepository.create(album);
|
return _albumRepository.create(album);
|
||||||
@ -294,7 +296,7 @@ class AlbumService {
|
|||||||
|
|
||||||
Future<bool> deleteAlbum(Album album) async {
|
Future<bool> deleteAlbum(Album album) async {
|
||||||
try {
|
try {
|
||||||
final userId = Store.get(StoreKey.currentUser).isarId;
|
final userId = Store.get(StoreKey.currentUser).id;
|
||||||
if (album.owner.value?.isarId == userId) {
|
if (album.owner.value?.isarId == userId) {
|
||||||
await _albumApiRepository.delete(album.remoteId!);
|
await _albumApiRepository.delete(album.remoteId!);
|
||||||
}
|
}
|
||||||
@ -356,15 +358,15 @@ class AlbumService {
|
|||||||
|
|
||||||
Future<bool> removeUser(
|
Future<bool> removeUser(
|
||||||
Album album,
|
Album album,
|
||||||
User user,
|
UserDto user,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
await _albumApiRepository.removeUser(
|
await _albumApiRepository.removeUser(
|
||||||
album.remoteId!,
|
album.remoteId!,
|
||||||
userId: user.id,
|
userId: user.uid,
|
||||||
);
|
);
|
||||||
|
|
||||||
album.sharedUsers.remove(user);
|
album.sharedUsers.remove(entity.User.fromDto(user));
|
||||||
await _albumRepository.removeUsers(album, [user]);
|
await _albumRepository.removeUsers(album, [user]);
|
||||||
final a = await _albumRepository.get(album.id);
|
final a = await _albumRepository.get(album.id);
|
||||||
// trigger watcher
|
// trigger watcher
|
||||||
@ -388,7 +390,10 @@ class AlbumService {
|
|||||||
album.sharedUsers.addAll(updatedAlbum.remoteUsers);
|
album.sharedUsers.addAll(updatedAlbum.remoteUsers);
|
||||||
album.shared = true;
|
album.shared = true;
|
||||||
|
|
||||||
await _albumRepository.addUsers(album, album.sharedUsers.toList());
|
await _albumRepository.addUsers(
|
||||||
|
album,
|
||||||
|
album.sharedUsers.map((u) => u.toDto()).toList(),
|
||||||
|
);
|
||||||
await _albumRepository.update(album);
|
await _albumRepository.update(album);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -5,24 +5,27 @@ import 'package:collection/collection.dart';
|
|||||||
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/domain/interfaces/exif.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
import 'package:immich_mobile/entities/backup_album.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/interfaces/asset_api.interface.dart';
|
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/asset_media.interface.dart';
|
import 'package:immich_mobile/interfaces/asset_media.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
|
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
|
||||||
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/backup.repository.dart';
|
import 'package:immich_mobile/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/etag.repository.dart';
|
import 'package:immich_mobile/repositories/etag.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
|
||||||
import 'package:immich_mobile/services/album.service.dart';
|
import 'package:immich_mobile/services/album.service.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/backup.service.dart';
|
import 'package:immich_mobile/services/backup.service.dart';
|
||||||
@ -45,6 +48,7 @@ final assetServiceProvider = Provider(
|
|||||||
ref.watch(userServiceProvider),
|
ref.watch(userServiceProvider),
|
||||||
ref.watch(backupServiceProvider),
|
ref.watch(backupServiceProvider),
|
||||||
ref.watch(albumServiceProvider),
|
ref.watch(albumServiceProvider),
|
||||||
|
ref.watch(storeServiceProvider),
|
||||||
ref.watch(assetMediaRepositoryProvider),
|
ref.watch(assetMediaRepositoryProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -61,6 +65,7 @@ class AssetService {
|
|||||||
final UserService _userService;
|
final UserService _userService;
|
||||||
final BackupService _backupService;
|
final BackupService _backupService;
|
||||||
final AlbumService _albumService;
|
final AlbumService _albumService;
|
||||||
|
final StoreService _storeService;
|
||||||
final IAssetMediaRepository _assetMediaRepository;
|
final IAssetMediaRepository _assetMediaRepository;
|
||||||
final log = Logger('AssetService');
|
final log = Logger('AssetService');
|
||||||
|
|
||||||
@ -76,6 +81,7 @@ class AssetService {
|
|||||||
this._userService,
|
this._userService,
|
||||||
this._backupService,
|
this._backupService,
|
||||||
this._albumService,
|
this._albumService,
|
||||||
|
this._storeService,
|
||||||
this._assetMediaRepository,
|
this._assetMediaRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -83,9 +89,9 @@ class AssetService {
|
|||||||
/// required. Returns `true` if there were any changes.
|
/// required. Returns `true` if there were any changes.
|
||||||
Future<bool> refreshRemoteAssets() async {
|
Future<bool> refreshRemoteAssets() async {
|
||||||
final syncedUserIds = await _etagRepository.getAllIds();
|
final syncedUserIds = await _etagRepository.getAllIds();
|
||||||
final List<User> syncedUsers = syncedUserIds.isEmpty
|
final List<UserDto> syncedUsers = syncedUserIds.isEmpty
|
||||||
? []
|
? []
|
||||||
: await _userRepository.getByIds(syncedUserIds);
|
: (await _userRepository.getByUserIds(syncedUserIds)).nonNulls.toList();
|
||||||
final Stopwatch sw = Stopwatch()..start();
|
final Stopwatch sw = Stopwatch()..start();
|
||||||
final bool changes = await _syncService.syncRemoteAssetsToDb(
|
final bool changes = await _syncService.syncRemoteAssetsToDb(
|
||||||
users: syncedUsers,
|
users: syncedUsers,
|
||||||
@ -99,10 +105,10 @@ class AssetService {
|
|||||||
|
|
||||||
/// Returns `(null, null)` if changes are invalid -> requires full sync
|
/// Returns `(null, null)` if changes are invalid -> requires full sync
|
||||||
Future<(List<Asset>? toUpsert, List<String>? toDelete)>
|
Future<(List<Asset>? toUpsert, List<String>? toDelete)>
|
||||||
_getRemoteAssetChanges(List<User> users, DateTime since) async {
|
_getRemoteAssetChanges(List<UserDto> users, DateTime since) async {
|
||||||
final dto = AssetDeltaSyncDto(
|
final dto = AssetDeltaSyncDto(
|
||||||
updatedAfter: since,
|
updatedAfter: since,
|
||||||
userIds: users.map((e) => e.id).toList(),
|
userIds: users.map((e) => e.uid).toList(),
|
||||||
);
|
);
|
||||||
final changes = await _apiService.syncApi.getDeltaSync(dto);
|
final changes = await _apiService.syncApi.getDeltaSync(dto);
|
||||||
return changes == null || changes.needsFullSync
|
return changes == null || changes.needsFullSync
|
||||||
@ -132,7 +138,7 @@ class AssetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `null` if the server state did not change, else list of assets
|
/// Returns `null` if the server state did not change, else list of assets
|
||||||
Future<List<Asset>?> _getRemoteAssets(User user, DateTime until) async {
|
Future<List<Asset>?> _getRemoteAssets(UserDto user, DateTime until) async {
|
||||||
const int chunkSize = 10000;
|
const int chunkSize = 10000;
|
||||||
try {
|
try {
|
||||||
final List<Asset> allAssets = [];
|
final List<Asset> allAssets = [];
|
||||||
@ -143,7 +149,7 @@ class AssetService {
|
|||||||
limit: chunkSize,
|
limit: chunkSize,
|
||||||
updatedUntil: until,
|
updatedUntil: until,
|
||||||
lastId: lastId,
|
lastId: lastId,
|
||||||
userId: user.id,
|
userId: user.uid,
|
||||||
);
|
);
|
||||||
log.fine("Requesting $chunkSize assets from $lastId");
|
log.fine("Requesting $chunkSize assets from $lastId");
|
||||||
final List<AssetResponseDto>? assets =
|
final List<AssetResponseDto>? assets =
|
||||||
@ -314,9 +320,9 @@ class AssetService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await refreshRemoteAssets();
|
await refreshRemoteAssets();
|
||||||
final owner = await _userRepository.me();
|
final owner = _storeService.get(StoreKey.currentUser);
|
||||||
final remoteAssets = await _assetRepository.getAll(
|
final remoteAssets = await _assetRepository.getAll(
|
||||||
ownerId: owner.isarId,
|
ownerId: owner.id,
|
||||||
state: AssetState.merged,
|
state: AssetState.merged,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -519,13 +525,13 @@ class AssetService {
|
|||||||
return _assetRepository.watchAsset(id, fireImmediately: fireImmediately);
|
return _assetRepository.watchAsset(id, fireImmediately: fireImmediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Asset>> getRecentlyAddedAssets() async {
|
Future<List<Asset>> getRecentlyAddedAssets() {
|
||||||
final me = await _userRepository.me();
|
final me = _storeService.get(StoreKey.currentUser);
|
||||||
return _assetRepository.getRecentlyAddedAssets(me.isarId);
|
return _assetRepository.getRecentlyAddedAssets(me.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Asset>> getMotionAssets() async {
|
Future<List<Asset>> getMotionAssets() {
|
||||||
final me = await _userRepository.me();
|
final me = _storeService.get(StoreKey.currentUser);
|
||||||
return _assetRepository.getMotionAssets(me.isarId);
|
return _assetRepository.getMotionAssets(me.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,15 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
|
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
|
||||||
|
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
||||||
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
||||||
@ -32,9 +36,9 @@ import 'package:immich_mobile/repositories/backup.repository.dart';
|
|||||||
import 'package:immich_mobile/repositories/etag.repository.dart';
|
import 'package:immich_mobile/repositories/etag.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/network.repository.dart';
|
import 'package:immich_mobile/repositories/network.repository.dart';
|
||||||
|
import 'package:immich_mobile/repositories/partner.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
import 'package:immich_mobile/repositories/permission.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
|
||||||
import 'package:immich_mobile/repositories/user_api.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/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
@ -385,7 +389,7 @@ class BackgroundService {
|
|||||||
AlbumMediaRepository albumMediaRepository = AlbumMediaRepository();
|
AlbumMediaRepository albumMediaRepository = AlbumMediaRepository();
|
||||||
FileMediaRepository fileMediaRepository = FileMediaRepository();
|
FileMediaRepository fileMediaRepository = FileMediaRepository();
|
||||||
AssetMediaRepository assetMediaRepository = AssetMediaRepository();
|
AssetMediaRepository assetMediaRepository = AssetMediaRepository();
|
||||||
UserRepository userRepository = UserRepository(db);
|
IUserRepository userRepository = IsarUserRepository(db);
|
||||||
UserApiRepository userApiRepository =
|
UserApiRepository userApiRepository =
|
||||||
UserApiRepository(apiService.usersApi);
|
UserApiRepository(apiService.usersApi);
|
||||||
AlbumApiRepository albumApiRepository =
|
AlbumApiRepository albumApiRepository =
|
||||||
@ -396,6 +400,7 @@ class BackgroundService {
|
|||||||
HashService(assetRepository, this, albumMediaRepository);
|
HashService(assetRepository, this, albumMediaRepository);
|
||||||
EntityService entityService =
|
EntityService entityService =
|
||||||
EntityService(assetRepository, userRepository);
|
EntityService(assetRepository, userRepository);
|
||||||
|
IPartnerRepository partnerRepository = PartnerRepository(db);
|
||||||
SyncService syncSerive = SyncService(
|
SyncService syncSerive = SyncService(
|
||||||
hashService,
|
hashService,
|
||||||
entityService,
|
entityService,
|
||||||
@ -404,7 +409,9 @@ class BackgroundService {
|
|||||||
albumRepository,
|
albumRepository,
|
||||||
assetRepository,
|
assetRepository,
|
||||||
exifInfoRepository,
|
exifInfoRepository,
|
||||||
|
partnerRepository,
|
||||||
userRepository,
|
userRepository,
|
||||||
|
StoreService.I,
|
||||||
eTagRepository,
|
eTagRepository,
|
||||||
);
|
);
|
||||||
UserService userService = UserService(
|
UserService userService = UserService(
|
||||||
|
@ -34,7 +34,7 @@ class BackupVerificationService {
|
|||||||
|
|
||||||
/// Returns at most [limit] assets that were backed up without exif
|
/// Returns at most [limit] assets that were backed up without exif
|
||||||
Future<List<Asset>> findWronglyBackedUpAssets({int limit = 100}) async {
|
Future<List<Asset>> findWronglyBackedUpAssets({int limit = 100}) async {
|
||||||
final owner = Store.get(StoreKey.currentUser).isarId;
|
final owner = Store.get(StoreKey.currentUser).id;
|
||||||
final List<Asset> onlyLocal = await _assetRepository.getAll(
|
final List<Asset> onlyLocal = await _assetRepository.getAll(
|
||||||
ownerId: owner,
|
ownerId: owner,
|
||||||
state: AssetState.local,
|
state: AssetState.local,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
|
||||||
|
|
||||||
class EntityService {
|
class EntityService {
|
||||||
final IAssetRepository _assetRepository;
|
final IAssetRepository _assetRepository;
|
||||||
@ -17,7 +18,8 @@ class EntityService {
|
|||||||
final ownerId = album.ownerId;
|
final ownerId = album.ownerId;
|
||||||
if (ownerId != null) {
|
if (ownerId != null) {
|
||||||
// replace owner with user from database
|
// replace owner with user from database
|
||||||
album.owner.value = await _userRepository.get(ownerId);
|
final user = await _userRepository.getByUserId(ownerId);
|
||||||
|
album.owner.value = user == null ? null : User.fromDto(user);
|
||||||
}
|
}
|
||||||
final thumbnailAssetId =
|
final thumbnailAssetId =
|
||||||
album.remoteThumbnailAssetId ?? album.thumbnail.value?.remoteId;
|
album.remoteThumbnailAssetId ?? album.thumbnail.value?.remoteId;
|
||||||
@ -29,9 +31,9 @@ class EntityService {
|
|||||||
if (album.remoteUsers.isNotEmpty) {
|
if (album.remoteUsers.isNotEmpty) {
|
||||||
// replace all users with users from database
|
// replace all users with users from database
|
||||||
final users = await _userRepository
|
final users = await _userRepository
|
||||||
.getByIds(album.remoteUsers.map((user) => user.id).toList());
|
.getByUserIds(album.remoteUsers.map((user) => user.id).toList());
|
||||||
album.sharedUsers.clear();
|
album.sharedUsers.clear();
|
||||||
album.sharedUsers.addAll(users);
|
album.sharedUsers.addAll(users.nonNulls.map(User.fromDto));
|
||||||
album.shared = true;
|
album.shared = true;
|
||||||
}
|
}
|
||||||
if (album.remoteAssets.isNotEmpty) {
|
if (album.remoteAssets.isNotEmpty) {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
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/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/partner.repository.dart';
|
import 'package:immich_mobile/repositories/partner.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
final partnerServiceProvider = Provider(
|
final partnerServiceProvider = Provider(
|
||||||
@ -28,57 +28,58 @@ class PartnerService {
|
|||||||
this._partnerRepository,
|
this._partnerRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<User>> getSharedWith() async {
|
Future<List<UserDto>> getSharedWith() async {
|
||||||
return _partnerRepository.getSharedWith();
|
return _partnerRepository.getSharedWith();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<User>> getSharedBy() async {
|
Future<List<UserDto>> getSharedBy() async {
|
||||||
return _partnerRepository.getSharedBy();
|
return _partnerRepository.getSharedBy();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<User>> watchSharedWith() {
|
Stream<List<UserDto>> watchSharedWith() {
|
||||||
return _partnerRepository.watchSharedWith();
|
return _partnerRepository.watchSharedWith();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<User>> watchSharedBy() {
|
Stream<List<UserDto>> watchSharedBy() {
|
||||||
return _partnerRepository.watchSharedBy();
|
return _partnerRepository.watchSharedBy();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> removePartner(User partner) async {
|
Future<bool> removePartner(UserDto partner) async {
|
||||||
try {
|
try {
|
||||||
await _partnerApiRepository.delete(partner.id);
|
await _partnerApiRepository.delete(partner.uid);
|
||||||
partner.isPartnerSharedBy = false;
|
await _userRepository.update(partner.copyWith(isPartnerSharedBy: false));
|
||||||
await _userRepository.update(partner);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log.warning("Failed to remove partner ${partner.id}", e);
|
_log.warning("Failed to remove partner ${partner.uid}", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addPartner(User partner) async {
|
Future<bool> addPartner(UserDto partner) async {
|
||||||
try {
|
try {
|
||||||
await _partnerApiRepository.create(partner.id);
|
await _partnerApiRepository.create(partner.uid);
|
||||||
partner.isPartnerSharedBy = true;
|
await _userRepository.update(partner.copyWith(isPartnerSharedBy: true));
|
||||||
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.uid}", e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updatePartner(User partner, {required bool inTimeline}) async {
|
Future<bool> updatePartner(
|
||||||
|
UserDto partner, {
|
||||||
|
required bool inTimeline,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
final dto = await _partnerApiRepository.update(
|
final dto = await _partnerApiRepository.update(
|
||||||
partner.id,
|
partner.uid,
|
||||||
inTimeline: inTimeline,
|
inTimeline: inTimeline,
|
||||||
);
|
);
|
||||||
partner.inTimeline = dto.inTimeline;
|
await _userRepository
|
||||||
await _userRepository.update(partner);
|
.update(partner.copyWith(inTimeline: dto.inTimeline));
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log.warning("Failed to update partner ${partner.id}", e);
|
_log.warning("Failed to update partner ${partner.uid}", e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -3,24 +3,29 @@ import 'dart:async';
|
|||||||
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:immich_mobile/domain/interfaces/exif.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.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/etag.entity.dart';
|
import 'package:immich_mobile/entities/etag.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
||||||
import 'package:immich_mobile/interfaces/album.interface.dart';
|
import 'package:immich_mobile/interfaces/album.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/album_api.interface.dart';
|
import 'package:immich_mobile/interfaces/album_api.interface.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/interfaces/asset.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/album.repository.dart';
|
import 'package:immich_mobile/repositories/album.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/album_api.repository.dart';
|
import 'package:immich_mobile/repositories/album_api.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/asset.repository.dart';
|
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/etag.repository.dart';
|
import 'package:immich_mobile/repositories/etag.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
import 'package:immich_mobile/repositories/partner.repository.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';
|
||||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||||
@ -37,7 +42,9 @@ final syncServiceProvider = Provider(
|
|||||||
ref.watch(albumRepositoryProvider),
|
ref.watch(albumRepositoryProvider),
|
||||||
ref.watch(assetRepositoryProvider),
|
ref.watch(assetRepositoryProvider),
|
||||||
ref.watch(exifRepositoryProvider),
|
ref.watch(exifRepositoryProvider),
|
||||||
|
ref.watch(partnerRepositoryProvider),
|
||||||
ref.watch(userRepositoryProvider),
|
ref.watch(userRepositoryProvider),
|
||||||
|
ref.watch(storeServiceProvider),
|
||||||
ref.watch(etagRepositoryProvider),
|
ref.watch(etagRepositoryProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -51,6 +58,8 @@ class SyncService {
|
|||||||
final IAssetRepository _assetRepository;
|
final IAssetRepository _assetRepository;
|
||||||
final IExifInfoRepository _exifInfoRepository;
|
final IExifInfoRepository _exifInfoRepository;
|
||||||
final IUserRepository _userRepository;
|
final IUserRepository _userRepository;
|
||||||
|
final IPartnerRepository _partnerRepository;
|
||||||
|
final StoreService _storeService;
|
||||||
final IETagRepository _eTagRepository;
|
final IETagRepository _eTagRepository;
|
||||||
final AsyncMutex _lock = AsyncMutex();
|
final AsyncMutex _lock = AsyncMutex();
|
||||||
final Logger _log = Logger('SyncService');
|
final Logger _log = Logger('SyncService');
|
||||||
@ -63,7 +72,9 @@ class SyncService {
|
|||||||
this._albumRepository,
|
this._albumRepository,
|
||||||
this._assetRepository,
|
this._assetRepository,
|
||||||
this._exifInfoRepository,
|
this._exifInfoRepository,
|
||||||
|
this._partnerRepository,
|
||||||
this._userRepository,
|
this._userRepository,
|
||||||
|
this._storeService,
|
||||||
this._eTagRepository,
|
this._eTagRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -71,20 +82,20 @@ class SyncService {
|
|||||||
|
|
||||||
/// Syncs users from the server to the local database
|
/// Syncs users from the server to the local database
|
||||||
/// Returns `true`if there were any changes
|
/// Returns `true`if there were any changes
|
||||||
Future<bool> syncUsersFromServer(List<User> users) =>
|
Future<bool> syncUsersFromServer(List<UserDto> users) =>
|
||||||
_lock.run(() => _syncUsersFromServer(users));
|
_lock.run(() => _syncUsersFromServer(users));
|
||||||
|
|
||||||
/// Syncs remote assets owned by the logged-in user to the DB
|
/// Syncs remote assets owned by the logged-in user to the DB
|
||||||
/// Returns `true` if there were any changes
|
/// Returns `true` if there were any changes
|
||||||
Future<bool> syncRemoteAssetsToDb({
|
Future<bool> syncRemoteAssetsToDb({
|
||||||
required List<User> users,
|
required List<UserDto> users,
|
||||||
required Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
|
required Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
|
||||||
List<User> users,
|
List<UserDto> users,
|
||||||
DateTime since,
|
DateTime since,
|
||||||
) getChangedAssets,
|
) getChangedAssets,
|
||||||
required FutureOr<List<Asset>?> Function(User user, DateTime until)
|
required FutureOr<List<Asset>?> Function(UserDto user, DateTime until)
|
||||||
loadAssets,
|
loadAssets,
|
||||||
required FutureOr<List<User>?> Function() refreshUsers,
|
required FutureOr<List<UserDto>?> Function() refreshUsers,
|
||||||
}) =>
|
}) =>
|
||||||
_lock.run(
|
_lock.run(
|
||||||
() async =>
|
() async =>
|
||||||
@ -134,16 +145,16 @@ class SyncService {
|
|||||||
|
|
||||||
/// Syncs users from the server to the local database
|
/// Syncs users from the server to the local database
|
||||||
/// Returns `true`if there were any changes
|
/// Returns `true`if there were any changes
|
||||||
Future<bool> _syncUsersFromServer(List<User> users) async {
|
Future<bool> _syncUsersFromServer(List<UserDto> users) async {
|
||||||
users.sortBy((u) => u.id);
|
users.sortBy((u) => u.uid);
|
||||||
final dbUsers = await _userRepository.getAll(sortBy: UserSort.id);
|
final dbUsers = await _userRepository.getAll(sortBy: SortUserBy.id);
|
||||||
final List<int> toDelete = [];
|
final List<int> toDelete = [];
|
||||||
final List<User> toUpsert = [];
|
final List<UserDto> toUpsert = [];
|
||||||
final changes = diffSortedListsSync(
|
final changes = diffSortedListsSync(
|
||||||
users,
|
users,
|
||||||
dbUsers,
|
dbUsers,
|
||||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid),
|
||||||
both: (User a, User b) {
|
both: (UserDto a, UserDto b) {
|
||||||
if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) ||
|
if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) ||
|
||||||
a.isPartnerSharedBy != b.isPartnerSharedBy ||
|
a.isPartnerSharedBy != b.isPartnerSharedBy ||
|
||||||
a.isPartnerSharedWith != b.isPartnerSharedWith ||
|
a.isPartnerSharedWith != b.isPartnerSharedWith ||
|
||||||
@ -153,13 +164,13 @@ class SyncService {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
onlyFirst: (User a) => toUpsert.add(a),
|
onlyFirst: (UserDto a) => toUpsert.add(a),
|
||||||
onlySecond: (User b) => toDelete.add(b.isarId),
|
onlySecond: (UserDto b) => toDelete.add(b.id),
|
||||||
);
|
);
|
||||||
if (changes) {
|
if (changes) {
|
||||||
await _userRepository.transaction(() async {
|
await _userRepository.transaction(() async {
|
||||||
await _userRepository.deleteById(toDelete);
|
await _userRepository.delete(toDelete);
|
||||||
await _userRepository.upsertAll(toUpsert);
|
await _userRepository.updateAll(toUpsert);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return changes;
|
return changes;
|
||||||
@ -185,15 +196,15 @@ class SyncService {
|
|||||||
|
|
||||||
/// Efficiently syncs assets via changes. Returns `null` when a full sync is required.
|
/// Efficiently syncs assets via changes. Returns `null` when a full sync is required.
|
||||||
Future<bool?> _syncRemoteAssetChanges(
|
Future<bool?> _syncRemoteAssetChanges(
|
||||||
List<User> users,
|
List<UserDto> users,
|
||||||
Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
|
Future<(List<Asset>? toUpsert, List<String>? toDelete)> Function(
|
||||||
List<User> users,
|
List<UserDto> users,
|
||||||
DateTime since,
|
DateTime since,
|
||||||
) getChangedAssets,
|
) getChangedAssets,
|
||||||
) async {
|
) async {
|
||||||
final currentUser = await _userRepository.me();
|
final currentUser = _storeService.get(StoreKey.currentUser);
|
||||||
final DateTime? since =
|
final DateTime? since =
|
||||||
(await _eTagRepository.get(currentUser.isarId))?.time?.toUtc();
|
(await _eTagRepository.get(currentUser.id))?.time?.toUtc();
|
||||||
if (since == null) return null;
|
if (since == null) return null;
|
||||||
final DateTime now = DateTime.now();
|
final DateTime now = DateTime.now();
|
||||||
final (toUpsert, toDelete) = await getChangedAssets(users, since);
|
final (toUpsert, toDelete) = await getChangedAssets(users, since);
|
||||||
@ -240,10 +251,16 @@ class SyncService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<UserDto>> _getAllAccessibleUsers() async {
|
||||||
|
final sharedWith = (await _partnerRepository.getSharedWith()).toSet();
|
||||||
|
sharedWith.add(_storeService.get(StoreKey.currentUser));
|
||||||
|
return sharedWith.toList();
|
||||||
|
}
|
||||||
|
|
||||||
/// Syncs assets by loading and comparing all assets from the server.
|
/// Syncs assets by loading and comparing all assets from the server.
|
||||||
Future<bool> _syncRemoteAssetsFull(
|
Future<bool> _syncRemoteAssetsFull(
|
||||||
FutureOr<List<User>?> Function() refreshUsers,
|
FutureOr<List<UserDto>?> Function() refreshUsers,
|
||||||
FutureOr<List<Asset>?> Function(User user, DateTime until) loadAssets,
|
FutureOr<List<Asset>?> Function(UserDto user, DateTime until) loadAssets,
|
||||||
) async {
|
) async {
|
||||||
final serverUsers = await refreshUsers();
|
final serverUsers = await refreshUsers();
|
||||||
if (serverUsers == null) {
|
if (serverUsers == null) {
|
||||||
@ -251,17 +268,17 @@ class SyncService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await _syncUsersFromServer(serverUsers);
|
await _syncUsersFromServer(serverUsers);
|
||||||
final List<User> users = await _userRepository.getAllAccessible();
|
final List<UserDto> users = await _getAllAccessibleUsers();
|
||||||
bool changes = false;
|
bool changes = false;
|
||||||
for (User u in users) {
|
for (UserDto u in users) {
|
||||||
changes |= await _syncRemoteAssetsForUser(u, loadAssets);
|
changes |= await _syncRemoteAssetsForUser(u, loadAssets);
|
||||||
}
|
}
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _syncRemoteAssetsForUser(
|
Future<bool> _syncRemoteAssetsForUser(
|
||||||
User user,
|
UserDto user,
|
||||||
FutureOr<List<Asset>?> Function(User user, DateTime until) loadAssets,
|
FutureOr<List<Asset>?> Function(UserDto user, DateTime until) loadAssets,
|
||||||
) async {
|
) async {
|
||||||
final DateTime now = DateTime.now().toUtc();
|
final DateTime now = DateTime.now().toUtc();
|
||||||
final List<Asset>? remote = await loadAssets(user, now);
|
final List<Asset>? remote = await loadAssets(user, now);
|
||||||
@ -269,7 +286,7 @@ class SyncService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final List<Asset> inDb = await _assetRepository.getAll(
|
final List<Asset> inDb = await _assetRepository.getAll(
|
||||||
ownerId: user.isarId,
|
ownerId: user.id,
|
||||||
sortBy: AssetSort.checksum,
|
sortBy: AssetSort.checksum,
|
||||||
);
|
);
|
||||||
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
|
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
|
||||||
@ -295,13 +312,13 @@ class SyncService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateUserAssetsETag(List<User> users, DateTime time) {
|
Future<void> _updateUserAssetsETag(List<UserDto> users, DateTime time) {
|
||||||
final etags = users.map((u) => ETag(id: u.id, time: time)).toList();
|
final etags = users.map((u) => ETag(id: u.uid, time: time)).toList();
|
||||||
return _eTagRepository.upsertAll(etags);
|
return _eTagRepository.upsertAll(etags);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _clearUserAssetsETag(List<User> users) {
|
Future<void> _clearUserAssetsETag(List<UserDto> users) {
|
||||||
final ids = users.map((u) => u.id).toList();
|
final ids = users.map((u) => u.uid).toList();
|
||||||
return _eTagRepository.deleteByIds(ids);
|
return _eTagRepository.deleteByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,26 +390,27 @@ class SyncService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// update shared users
|
// update shared users
|
||||||
final List<User> sharedUsers = album.sharedUsers.toList(growable: false);
|
final List<UserDto> sharedUsers =
|
||||||
|
album.sharedUsers.map((u) => u.toDto()).toList(growable: false);
|
||||||
sharedUsers.sort((a, b) => a.id.compareTo(b.id));
|
sharedUsers.sort((a, b) => a.id.compareTo(b.id));
|
||||||
final List<User> users = dto.remoteUsers.toList()
|
final List<UserDto> users = dto.remoteUsers.map((u) => u.toDto()).toList()
|
||||||
..sort((a, b) => a.id.compareTo(b.id));
|
..sort((a, b) => a.id.compareTo(b.id));
|
||||||
final List<String> userIdsToAdd = [];
|
final List<String> userIdsToAdd = [];
|
||||||
final List<User> usersToUnlink = [];
|
final List<UserDto> usersToUnlink = [];
|
||||||
diffSortedListsSync(
|
diffSortedListsSync(
|
||||||
users,
|
users,
|
||||||
sharedUsers,
|
sharedUsers,
|
||||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
compare: (UserDto a, UserDto b) => a.id.compareTo(b.id),
|
||||||
both: (a, b) => false,
|
both: (a, b) => false,
|
||||||
onlyFirst: (User a) => userIdsToAdd.add(a.id),
|
onlyFirst: (UserDto a) => userIdsToAdd.add(a.uid),
|
||||||
onlySecond: (User a) => usersToUnlink.add(a),
|
onlySecond: (UserDto a) => usersToUnlink.add(a),
|
||||||
);
|
);
|
||||||
|
|
||||||
// for shared album: put missing album assets into local DB
|
// for shared album: put missing album assets into local DB
|
||||||
final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd);
|
final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd);
|
||||||
await upsertAssetsWithExif(updated);
|
await upsertAssetsWithExif(updated);
|
||||||
final assetsToLink = existingInDb + updated;
|
final assetsToLink = existingInDb + updated;
|
||||||
final usersToLink = await _userRepository.getByIds(userIdsToAdd);
|
final usersToLink = await _userRepository.getByUserIds(userIdsToAdd);
|
||||||
|
|
||||||
album.name = dto.name;
|
album.name = dto.name;
|
||||||
album.shared = dto.shared;
|
album.shared = dto.shared;
|
||||||
@ -416,7 +434,7 @@ class SyncService {
|
|||||||
try {
|
try {
|
||||||
await _assetRepository.transaction(() async {
|
await _assetRepository.transaction(() async {
|
||||||
await _assetRepository.updateAll(toUpdate);
|
await _assetRepository.updateAll(toUpdate);
|
||||||
await _albumRepository.addUsers(album, usersToLink);
|
await _albumRepository.addUsers(album, usersToLink.nonNulls.toList());
|
||||||
await _albumRepository.removeUsers(album, usersToUnlink);
|
await _albumRepository.removeUsers(album, usersToUnlink);
|
||||||
await _albumRepository.addAssets(album, assetsToLink);
|
await _albumRepository.addAssets(album, assetsToLink);
|
||||||
await _albumRepository.removeAssets(album, toUnlink);
|
await _albumRepository.removeAssets(album, toUnlink);
|
||||||
@ -429,7 +447,7 @@ class SyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (album.shared || dto.shared) {
|
if (album.shared || dto.shared) {
|
||||||
final userId = (await _userRepository.me()).isarId;
|
final userId = (_storeService.get(StoreKey.currentUser)).id;
|
||||||
final foreign =
|
final foreign =
|
||||||
await _assetRepository.getByAlbum(album, notOwnedBy: [userId]);
|
await _assetRepository.getByAlbum(album, notOwnedBy: [userId]);
|
||||||
existing.addAll(foreign);
|
existing.addAll(foreign);
|
||||||
@ -482,8 +500,7 @@ class SyncService {
|
|||||||
);
|
);
|
||||||
} else if (album.shared) {
|
} else if (album.shared) {
|
||||||
// delete assets in DB unless they belong to this user or are part of some other shared album or belong to a partner
|
// delete assets in DB unless they belong to this user or are part of some other shared album or belong to a partner
|
||||||
final userIds =
|
final userIds = (await _getAllAccessibleUsers()).map((user) => user.id);
|
||||||
(await _userRepository.getAllAccessible()).map((user) => user.isarId);
|
|
||||||
final orphanedAssets =
|
final orphanedAssets =
|
||||||
await _assetRepository.getByAlbum(album, notOwnedBy: userIds);
|
await _assetRepository.getByAlbum(album, notOwnedBy: userIds);
|
||||||
deleteCandidates.addAll(orphanedAssets);
|
deleteCandidates.addAll(orphanedAssets);
|
||||||
@ -566,7 +583,7 @@ class SyncService {
|
|||||||
// general case, e.g. some assets have been deleted or there are excluded albums on iOS
|
// general case, e.g. some assets have been deleted or there are excluded albums on iOS
|
||||||
final inDb = await _assetRepository.getByAlbum(
|
final inDb = await _assetRepository.getByAlbum(
|
||||||
dbAlbum,
|
dbAlbum,
|
||||||
ownerId: (await _userRepository.me()).isarId,
|
ownerId: (_storeService.get(StoreKey.currentUser)).id,
|
||||||
sortBy: AssetSort.checksum,
|
sortBy: AssetSort.checksum,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,41 +1,42 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.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/interfaces/timeline.interface.dart';
|
import 'package:immich_mobile/interfaces/timeline.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/timeline.repository.dart';
|
import 'package:immich_mobile/repositories/timeline.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.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';
|
||||||
|
|
||||||
final timelineServiceProvider = Provider<TimelineService>((ref) {
|
final timelineServiceProvider = Provider<TimelineService>((ref) {
|
||||||
return TimelineService(
|
return TimelineService(
|
||||||
ref.watch(timelineRepositoryProvider),
|
ref.watch(timelineRepositoryProvider),
|
||||||
ref.watch(userRepositoryProvider),
|
|
||||||
ref.watch(appSettingsServiceProvider),
|
ref.watch(appSettingsServiceProvider),
|
||||||
|
ref.watch(storeServiceProvider),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
class TimelineService {
|
class TimelineService {
|
||||||
final ITimelineRepository _timelineRepository;
|
final ITimelineRepository _timelineRepository;
|
||||||
final IUserRepository _userRepository;
|
|
||||||
final AppSettingsService _appSettingsService;
|
final AppSettingsService _appSettingsService;
|
||||||
|
final StoreService _storeService;
|
||||||
|
|
||||||
const TimelineService(
|
const TimelineService(
|
||||||
this._timelineRepository,
|
this._timelineRepository,
|
||||||
this._userRepository,
|
|
||||||
this._appSettingsService,
|
this._appSettingsService,
|
||||||
|
this._storeService,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<int>> getTimelineUserIds() async {
|
Future<List<int>> getTimelineUserIds() async {
|
||||||
final me = await _userRepository.me();
|
final me = _storeService.get(StoreKey.currentUser);
|
||||||
return _timelineRepository.getTimelineUserIds(me.isarId);
|
return _timelineRepository.getTimelineUserIds(me.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<int>> watchTimelineUserIds() async* {
|
Stream<List<int>> watchTimelineUserIds() async* {
|
||||||
final me = await _userRepository.me();
|
final me = _storeService.get(StoreKey.currentUser);
|
||||||
yield* _timelineRepository.watchTimelineUsers(me.isarId);
|
yield* _timelineRepository.watchTimelineUsers(me.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<RenderList> watchHomeTimeline(int userId) {
|
Stream<RenderList> watchHomeTimeline(int userId) {
|
||||||
@ -50,15 +51,15 @@ class TimelineService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<RenderList> watchArchiveTimeline() async* {
|
Stream<RenderList> watchArchiveTimeline() async* {
|
||||||
final user = await _userRepository.me();
|
final user = _storeService.get(StoreKey.currentUser);
|
||||||
|
|
||||||
yield* _timelineRepository.watchArchiveTimeline(user.isarId);
|
yield* _timelineRepository.watchArchiveTimeline(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<RenderList> watchFavoriteTimeline() async* {
|
Stream<RenderList> watchFavoriteTimeline() async* {
|
||||||
final user = await _userRepository.me();
|
final user = _storeService.get(StoreKey.currentUser);
|
||||||
|
|
||||||
yield* _timelineRepository.watchFavoriteTimeline(user.isarId);
|
yield* _timelineRepository.watchFavoriteTimeline(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<RenderList> watchAlbumTimeline(Album album) async* {
|
Stream<RenderList> watchAlbumTimeline(Album album) async* {
|
||||||
@ -69,9 +70,9 @@ class TimelineService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<RenderList> watchTrashTimeline() async* {
|
Stream<RenderList> watchTrashTimeline() async* {
|
||||||
final user = await _userRepository.me();
|
final user = _storeService.get(StoreKey.currentUser);
|
||||||
|
|
||||||
yield* _timelineRepository.watchTrashTimeline(user.isarId);
|
yield* _timelineRepository.watchTrashTimeline(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<RenderList> watchAllVideosTimeline() {
|
Stream<RenderList> watchAllVideosTimeline() {
|
||||||
@ -96,9 +97,9 @@ class TimelineService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream<RenderList> watchAssetSelectionTimeline() async* {
|
Stream<RenderList> watchAssetSelectionTimeline() async* {
|
||||||
final user = await _userRepository.me();
|
final user = _storeService.get(StoreKey.currentUser);
|
||||||
|
|
||||||
yield* _timelineRepository.watchAssetSelectionTimeline(user.isarId);
|
yield* _timelineRepository.watchAssetSelectionTimeline(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupAssetsBy _getGroupByOption() {
|
GroupAssetsBy _getGroupByOption() {
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.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/interfaces/asset.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/user.repository.dart';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@ -14,16 +13,20 @@ final trashServiceProvider = Provider<TrashService>((ref) {
|
|||||||
return TrashService(
|
return TrashService(
|
||||||
ref.watch(apiServiceProvider),
|
ref.watch(apiServiceProvider),
|
||||||
ref.watch(assetRepositoryProvider),
|
ref.watch(assetRepositoryProvider),
|
||||||
ref.watch(userRepositoryProvider),
|
ref.watch(storeServiceProvider),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
class TrashService {
|
class TrashService {
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
final IAssetRepository _assetRepository;
|
final IAssetRepository _assetRepository;
|
||||||
final IUserRepository _userRepository;
|
final StoreService _storeService;
|
||||||
|
|
||||||
TrashService(this._apiService, this._assetRepository, this._userRepository);
|
TrashService(
|
||||||
|
this._apiService,
|
||||||
|
this._assetRepository,
|
||||||
|
this._storeService,
|
||||||
|
);
|
||||||
|
|
||||||
Future<void> restoreAssets(Iterable<Asset> assetList) async {
|
Future<void> restoreAssets(Iterable<Asset> assetList) async {
|
||||||
final remoteAssets = assetList.where((a) => a.isRemote);
|
final remoteAssets = assetList.where((a) => a.isRemote);
|
||||||
@ -40,11 +43,11 @@ class TrashService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> emptyTrash() async {
|
Future<void> emptyTrash() async {
|
||||||
final user = await _userRepository.me();
|
final user = _storeService.get(StoreKey.currentUser);
|
||||||
|
|
||||||
await _apiService.trashApi.emptyTrash();
|
await _apiService.trashApi.emptyTrash();
|
||||||
|
|
||||||
final trashedAssets = await _assetRepository.getTrashAssets(user.isarId);
|
final trashedAssets = await _assetRepository.getTrashAssets(user.id);
|
||||||
final ids = trashedAssets.map((e) => e.remoteId!).toList();
|
final ids = trashedAssets.map((e) => e.remoteId!).toList();
|
||||||
|
|
||||||
await _assetRepository.transaction(() async {
|
await _assetRepository.transaction(() async {
|
||||||
@ -71,11 +74,11 @@ class TrashService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restoreTrash() async {
|
Future<void> restoreTrash() async {
|
||||||
final user = await _userRepository.me();
|
final user = _storeService.get(StoreKey.currentUser);
|
||||||
|
|
||||||
await _apiService.trashApi.restoreTrash();
|
await _apiService.trashApi.restoreTrash();
|
||||||
|
|
||||||
final trashedAssets = await _assetRepository.getTrashAssets(user.isarId);
|
final trashedAssets = await _assetRepository.getTrashAssets(user.id);
|
||||||
final updatedAssets = trashedAssets.map((asset) {
|
final updatedAssets = trashedAssets.map((asset) {
|
||||||
asset.isTrashed = false;
|
asset.isTrashed = false;
|
||||||
return asset;
|
return asset;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
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:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
|
||||||
import 'package:immich_mobile/interfaces/user_api.interface.dart';
|
import 'package:immich_mobile/interfaces/user_api.interface.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/partner_api.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_api.repository.dart';
|
import 'package:immich_mobile/repositories/user_api.repository.dart';
|
||||||
import 'package:immich_mobile/utils/diff.dart';
|
import 'package:immich_mobile/utils/diff.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@ -31,10 +31,6 @@ class UserService {
|
|||||||
this._userRepository,
|
this._userRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<User>> getUsers({bool self = false}) {
|
|
||||||
return _userRepository.getAll(self: self);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<({String profileImagePath})?> uploadProfileImage(XFile image) async {
|
Future<({String profileImagePath})?> uploadProfileImage(XFile image) async {
|
||||||
try {
|
try {
|
||||||
return await _userApiRepository.createProfileImage(
|
return await _userApiRepository.createProfileImage(
|
||||||
@ -47,17 +43,21 @@ class UserService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<User>?> getUsersFromServer() async {
|
Future<List<UserDto>> getAll() async {
|
||||||
List<User>? users;
|
return await _userRepository.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<UserDto>?> getUsersFromServer() async {
|
||||||
|
List<UserDto>? users;
|
||||||
try {
|
try {
|
||||||
users = await _userApiRepository.getAll();
|
users = await _userApiRepository.getAll();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log.warning("Failed to fetch users", e);
|
_log.warning("Failed to fetch users", e);
|
||||||
users = null;
|
users = null;
|
||||||
}
|
}
|
||||||
final List<User> sharedBy =
|
final List<UserDto> sharedBy =
|
||||||
await _partnerApiRepository.getAll(Direction.sharedByMe);
|
await _partnerApiRepository.getAll(Direction.sharedByMe);
|
||||||
final List<User> sharedWith =
|
final List<UserDto> sharedWith =
|
||||||
await _partnerApiRepository.getAll(Direction.sharedWithMe);
|
await _partnerApiRepository.getAll(Direction.sharedWithMe);
|
||||||
|
|
||||||
if (users == null) {
|
if (users == null) {
|
||||||
@ -65,36 +65,44 @@ class UserService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
users.sortBy((u) => u.id);
|
users.sortBy((u) => u.uid);
|
||||||
sharedBy.sortBy((u) => u.id);
|
sharedBy.sortBy((u) => u.uid);
|
||||||
sharedWith.sortBy((u) => u.id);
|
sharedWith.sortBy((u) => u.uid);
|
||||||
|
|
||||||
|
final updatedSharedBy = <UserDto>[];
|
||||||
|
|
||||||
diffSortedListsSync(
|
diffSortedListsSync(
|
||||||
users,
|
users,
|
||||||
sharedBy,
|
sharedBy,
|
||||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid),
|
||||||
both: (User a, User b) => a.isPartnerSharedBy = true,
|
both: (UserDto a, UserDto b) {
|
||||||
onlyFirst: (_) {},
|
updatedSharedBy.add(a.copyWith(isPartnerSharedBy: true));
|
||||||
onlySecond: (_) {},
|
|
||||||
);
|
|
||||||
|
|
||||||
diffSortedListsSync(
|
|
||||||
users,
|
|
||||||
sharedWith,
|
|
||||||
compare: (User a, User b) => a.id.compareTo(b.id),
|
|
||||||
both: (User a, User b) {
|
|
||||||
a.isPartnerSharedWith = true;
|
|
||||||
a.inTimeline = b.inTimeline;
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
onlyFirst: (_) {},
|
onlyFirst: (UserDto a) => updatedSharedBy.add(a),
|
||||||
onlySecond: (_) {},
|
onlySecond: (UserDto b) => updatedSharedBy.add(b),
|
||||||
);
|
);
|
||||||
|
|
||||||
return users;
|
final updatedSharedWith = <UserDto>[];
|
||||||
|
|
||||||
|
diffSortedListsSync(
|
||||||
|
updatedSharedBy,
|
||||||
|
sharedWith,
|
||||||
|
compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid),
|
||||||
|
both: (UserDto a, UserDto b) {
|
||||||
|
updatedSharedWith.add(
|
||||||
|
a.copyWith(inTimeline: b.inTimeline, isPartnerSharedWith: true),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onlyFirst: (UserDto a) => updatedSharedWith.add(a),
|
||||||
|
onlySecond: (UserDto b) => updatedSharedWith.add(b),
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedSharedWith;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearTable() {
|
Future<void> clearTable() {
|
||||||
return _userRepository.clearTable();
|
return _userRepository.deleteAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
|
|||||||
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
|
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/etag.entity.dart';
|
import 'package:immich_mobile/entities/etag.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/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
@ -5,8 +5,8 @@ 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/etag.entity.dart';
|
import 'package:immich_mobile/entities/etag.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
const int targetVersion = 8;
|
const int targetVersion = 8;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/activity_api.repository.dart';
|
import 'package:immich_mobile/repositories/activity_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/album_api.repository.dart';
|
import 'package:immich_mobile/repositories/album_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||||
@ -13,4 +14,5 @@ void invalidateAllApiRepositoryProviders(WidgetRef ref) {
|
|||||||
ref.invalidate(albumApiRepositoryProvider);
|
ref.invalidate(albumApiRepositoryProvider);
|
||||||
ref.invalidate(personApiRepositoryProvider);
|
ref.invalidate(personApiRepositoryProvider);
|
||||||
ref.invalidate(assetApiRepositoryProvider);
|
ref.invalidate(assetApiRepositoryProvider);
|
||||||
|
ref.invalidate(timelineUsersIdsProvider);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
// Add the owner name to the subtitle
|
// Add the owner name to the subtitle
|
||||||
String? owner;
|
String? owner;
|
||||||
if (showOwner) {
|
if (showOwner) {
|
||||||
if (album.ownerId == Store.get(StoreKey.currentUser).id) {
|
if (album.ownerId == Store.get(StoreKey.currentUser).uid) {
|
||||||
owner = 'album_thumbnail_owned'.tr();
|
owner = 'album_thumbnail_owned'.tr();
|
||||||
} else if (album.ownerName != null) {
|
} else if (album.ownerName != null) {
|
||||||
owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]);
|
owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]);
|
||||||
|
@ -5,25 +5,25 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.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/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/pages/editing/edit.page.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
import 'package:immich_mobile/services/stack.service.dart';
|
|
||||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart';
|
|
||||||
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/services/stack.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/immich_image.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
import 'package:immich_mobile/pages/editing/edit.page.dart';
|
|
||||||
|
|
||||||
class BottomGalleryBar extends ConsumerWidget {
|
class BottomGalleryBar extends ConsumerWidget {
|
||||||
final ValueNotifier<int> assetIndex;
|
final ValueNotifier<int> assetIndex;
|
||||||
@ -49,7 +49,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId;
|
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.id;
|
||||||
final showControls = ref.watch(showControlsProvider);
|
final showControls = ref.watch(showControlsProvider);
|
||||||
final stackId = asset.stackId;
|
final stackId = asset.stackId;
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class DescriptionInput extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return TextField(
|
return TextField(
|
||||||
enabled: owner?.isarId == asset.ownerId,
|
enabled: owner?.id == asset.ownerId,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
onTap: () => isFocus.value = true,
|
onTap: () => isFocus.value = true,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
|
@ -3,22 +3,22 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/tab.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/top_control_app_bar.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/trash.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/asset_grid/upload_dialog.dart';
|
|
||||||
import 'package:immich_mobile/providers/partner.provider.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/partner.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/tab.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/trash.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/upload_dialog.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_viewer/top_control_app_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
|
||||||
class GalleryAppBar extends ConsumerWidget {
|
class GalleryAppBar extends ConsumerWidget {
|
||||||
@ -33,12 +33,12 @@ class GalleryAppBar extends ConsumerWidget {
|
|||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
final album = ref.watch(currentAlbumProvider);
|
final album = ref.watch(currentAlbumProvider);
|
||||||
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId;
|
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.id;
|
||||||
final showControls = ref.watch(showControlsProvider);
|
final showControls = ref.watch(showControlsProvider);
|
||||||
|
|
||||||
final isPartner = ref
|
final isPartner = ref
|
||||||
.watch(partnerSharedWithProvider)
|
.watch(partnerSharedWithProvider)
|
||||||
.map((e) => e.isarId)
|
.map((e) => e.id)
|
||||||
.contains(asset.ownerId);
|
.contains(asset.ownerId);
|
||||||
|
|
||||||
toggleFavorite(Asset asset) =>
|
toggleFavorite(Asset asset) =>
|
||||||
|
@ -67,8 +67,9 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
|
|||||||
profileImagePath,
|
profileImagePath,
|
||||||
);
|
);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
user.profileImagePath = profileImagePath;
|
final updatedUser =
|
||||||
await Store.put(StoreKey.currentUser, user);
|
user.copyWith(profileImagePath: profileImagePath);
|
||||||
|
await Store.put(StoreKey.currentUser, updatedUser);
|
||||||
ref.read(currentUserProvider.notifier).refresh();
|
ref.read(currentUserProvider.notifier).refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
|
|
||||||
Widget userAvatar(BuildContext context, User u, {double? radius}) {
|
Widget userAvatar(BuildContext context, UserDto u, {double? radius}) {
|
||||||
final url =
|
final url =
|
||||||
"${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image";
|
"${Store.get(StoreKey.serverEndpoint)}/users/${u.uid}/profile-image";
|
||||||
final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
|
final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
radius: radius,
|
radius: radius,
|
||||||
|
@ -4,15 +4,15 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
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/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/widgets/common/transparent_image.dart';
|
import 'package:immich_mobile/widgets/common/transparent_image.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class UserCircleAvatar extends ConsumerWidget {
|
class UserCircleAvatar extends ConsumerWidget {
|
||||||
final User user;
|
final UserDto user;
|
||||||
double radius;
|
double radius;
|
||||||
double size;
|
double size;
|
||||||
|
|
||||||
@ -27,13 +27,13 @@ class UserCircleAvatar extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
bool isDarkTheme = context.themeData.brightness == Brightness.dark;
|
bool isDarkTheme = context.themeData.brightness == Brightness.dark;
|
||||||
final profileImageUrl =
|
final profileImageUrl =
|
||||||
'${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}';
|
'${Store.get(StoreKey.serverEndpoint)}/users/${user.uid}/profile-image?d=${Random().nextInt(1024)}';
|
||||||
|
|
||||||
final textIcon = DefaultTextStyle(
|
final textIcon = DefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary
|
color: isDarkTheme && user.avatarColor == AvatarColor.primary
|
||||||
? Colors.black
|
? Colors.black
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
),
|
),
|
||||||
@ -42,7 +42,7 @@ class UserCircleAvatar extends ConsumerWidget {
|
|||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
backgroundColor: user.avatarColor.toColor(),
|
backgroundColor: user.avatarColor.toColor(),
|
||||||
radius: radius,
|
radius: radius,
|
||||||
child: user.profileImagePath.isEmpty
|
child: user.profileImagePath == null
|
||||||
? textIcon
|
? textIcon
|
||||||
: ClipRRect(
|
: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||||
|
5
mobile/test/fixtures/album.stub.dart
vendored
5
mobile/test/fixtures/album.stub.dart
vendored
@ -1,4 +1,5 @@
|
|||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
|
|
||||||
import 'asset.stub.dart';
|
import 'asset.stub.dart';
|
||||||
import 'user.stub.dart';
|
import 'user.stub.dart';
|
||||||
@ -26,7 +27,7 @@ final class AlbumStub {
|
|||||||
shared: true,
|
shared: true,
|
||||||
activityEnabled: false,
|
activityEnabled: false,
|
||||||
endDate: DateTime(2020),
|
endDate: DateTime(2020),
|
||||||
)..sharedUsers.addAll([UserStub.admin]);
|
)..sharedUsers.addAll([User.fromDto(UserStub.admin)]);
|
||||||
|
|
||||||
static final oneAsset = Album(
|
static final oneAsset = Album(
|
||||||
name: "album-with-single-asset",
|
name: "album-with-single-asset",
|
||||||
@ -53,7 +54,7 @@ final class AlbumStub {
|
|||||||
)
|
)
|
||||||
..assets.addAll([AssetStub.image1, AssetStub.image2])
|
..assets.addAll([AssetStub.image1, AssetStub.image2])
|
||||||
..activityEnabled = true
|
..activityEnabled = true
|
||||||
..owner.value = UserStub.admin;
|
..owner.value = User.fromDto(UserStub.admin);
|
||||||
|
|
||||||
static final create2020end2020Album = Album(
|
static final create2020end2020Album = Album(
|
||||||
name: "create2020update2020Album",
|
name: "create2020update2020Album",
|
||||||
|
32
mobile/test/fixtures/user.stub.dart
vendored
32
mobile/test/fixtures/user.stub.dart
vendored
@ -1,35 +1,35 @@
|
|||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
abstract final class UserStub {
|
abstract final class UserStub {
|
||||||
const UserStub._();
|
const UserStub._();
|
||||||
|
|
||||||
static final admin = User(
|
static final admin = UserDto(
|
||||||
id: "admin",
|
uid: "admin",
|
||||||
updatedAt: DateTime(2021),
|
|
||||||
email: "admin@test.com",
|
email: "admin@test.com",
|
||||||
name: "admin",
|
name: "admin",
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
profileImagePath: '',
|
updatedAt: DateTime(2021),
|
||||||
avatarColor: AvatarColorEnum.green,
|
profileImagePath: null,
|
||||||
|
avatarColor: AvatarColor.green,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final user1 = User(
|
static final user1 = UserDto(
|
||||||
id: "user1",
|
uid: "user1",
|
||||||
updatedAt: DateTime(2022),
|
|
||||||
email: "user1@test.com",
|
email: "user1@test.com",
|
||||||
name: "user1",
|
name: "user1",
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
profileImagePath: '',
|
updatedAt: DateTime(2022),
|
||||||
avatarColor: AvatarColorEnum.red,
|
profileImagePath: null,
|
||||||
|
avatarColor: AvatarColor.red,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final user2 = User(
|
static final user2 = UserDto(
|
||||||
id: "user2",
|
uid: "user2",
|
||||||
updatedAt: DateTime(2023),
|
|
||||||
email: "user2@test.com",
|
email: "user2@test.com",
|
||||||
name: "user2",
|
name: "user2",
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
profileImagePath: '',
|
updatedAt: DateTime(2023),
|
||||||
avatarColor: AvatarColorEnum.primary,
|
profileImagePath: null,
|
||||||
|
avatarColor: AvatarColor.primary,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
@ -86,7 +86,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('converts user', () async {
|
test('converts user', () async {
|
||||||
User? user = await sut.tryGet(StoreKey.currentUser);
|
UserDto? user = await sut.tryGet(StoreKey.currentUser);
|
||||||
expect(user, isNull);
|
expect(user, isNull);
|
||||||
await sut.insert(StoreKey.currentUser, _kTestUser);
|
await sut.insert(StoreKey.currentUser, _kTestUser);
|
||||||
user = await sut.tryGet(StoreKey.currentUser);
|
user = await sut.tryGet(StoreKey.currentUser);
|
||||||
|
@ -9,7 +9,7 @@ import 'package:immich_mobile/domain/services/store.service.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:immich_mobile/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/models/activities/activity.model.dart';
|
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||||
import 'package:immich_mobile/pages/common/activities.page.dart';
|
import 'package:immich_mobile/pages/common/activities.page.dart';
|
||||||
@ -96,7 +96,7 @@ void main() {
|
|||||||
await db.writeTxn(() async {
|
await db.writeTxn(() async {
|
||||||
await db.clear();
|
await db.clear();
|
||||||
// Save all assets
|
// Save all assets
|
||||||
await db.users.put(UserStub.admin);
|
await db.users.put(User.fromDto(UserStub.admin));
|
||||||
await db.assets.putAll([AssetStub.image1, AssetStub.image2]);
|
await db.assets.putAll([AssetStub.image1, AssetStub.image2]);
|
||||||
await db.albums.put(AlbumStub.twoAsset);
|
await db.albums.put(AlbumStub.twoAsset);
|
||||||
await AlbumStub.twoAsset.owner.save();
|
await AlbumStub.twoAsset.owner.save();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
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/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockCurrentUserProvider extends StateNotifier<User?>
|
class MockCurrentUserProvider extends StateNotifier<UserDto?>
|
||||||
with Mock
|
with Mock
|
||||||
implements CurrentUserProvider {
|
implements CurrentUserProvider {
|
||||||
MockCurrentUserProvider() : super(null);
|
MockCurrentUserProvider() : super(null);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set state(User? user) => super.state = user;
|
set state(UserDto? user) => super.state = user;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/etag.entity.dart';
|
import 'package:immich_mobile/entities/etag.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
|
||||||
import 'package:immich_mobile/services/sync.service.dart';
|
import 'package:immich_mobile/services/sync.service.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
@ -56,8 +56,10 @@ void main() {
|
|||||||
final MockAlbumMediaRepository albumMediaRepository =
|
final MockAlbumMediaRepository albumMediaRepository =
|
||||||
MockAlbumMediaRepository();
|
MockAlbumMediaRepository();
|
||||||
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
|
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
|
||||||
final owner = User(
|
final MockPartnerRepository partnerRepository = MockPartnerRepository();
|
||||||
id: "1",
|
|
||||||
|
final owner = UserDto(
|
||||||
|
uid: "1",
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
email: "a@b.c",
|
email: "a@b.c",
|
||||||
name: "first last",
|
name: "first last",
|
||||||
@ -92,21 +94,22 @@ void main() {
|
|||||||
albumRepository,
|
albumRepository,
|
||||||
assetRepository,
|
assetRepository,
|
||||||
exifInfoRepository,
|
exifInfoRepository,
|
||||||
|
partnerRepository,
|
||||||
userRepository,
|
userRepository,
|
||||||
|
StoreService.I,
|
||||||
eTagRepository,
|
eTagRepository,
|
||||||
);
|
);
|
||||||
when(() => eTagRepository.get(owner.isarId))
|
when(() => eTagRepository.get(owner.id))
|
||||||
.thenAnswer((_) async => ETag(id: owner.id, time: DateTime.now()));
|
.thenAnswer((_) async => ETag(id: owner.uid, time: DateTime.now()));
|
||||||
when(() => eTagRepository.deleteByIds(["1"])).thenAnswer((_) async {});
|
when(() => eTagRepository.deleteByIds(["1"])).thenAnswer((_) async {});
|
||||||
when(() => eTagRepository.upsertAll(any())).thenAnswer((_) async {});
|
when(() => eTagRepository.upsertAll(any())).thenAnswer((_) async {});
|
||||||
when(() => userRepository.me()).thenAnswer((_) async => owner);
|
when(() => partnerRepository.getSharedWith()).thenAnswer((_) async => []);
|
||||||
when(() => userRepository.getAll(sortBy: UserSort.id))
|
when(() => userRepository.getAll(sortBy: SortUserBy.id))
|
||||||
.thenAnswer((_) async => [owner]);
|
|
||||||
when(() => userRepository.getAllAccessible())
|
|
||||||
.thenAnswer((_) async => [owner]);
|
.thenAnswer((_) async => [owner]);
|
||||||
|
when(() => userRepository.getAll()).thenAnswer((_) async => [owner]);
|
||||||
when(
|
when(
|
||||||
() => assetRepository.getAll(
|
() => assetRepository.getAll(
|
||||||
ownerId: owner.isarId,
|
ownerId: owner.id,
|
||||||
sortBy: AssetSort.checksum,
|
sortBy: AssetSort.checksum,
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async => initialAssets);
|
).thenAnswer((_) async => initialAssets);
|
||||||
@ -180,7 +183,7 @@ void main() {
|
|||||||
expect(c1, isTrue);
|
expect(c1, isTrue);
|
||||||
when(
|
when(
|
||||||
() => assetRepository.getAll(
|
() => assetRepository.getAll(
|
||||||
ownerId: owner.isarId,
|
ownerId: owner.id,
|
||||||
sortBy: AssetSort.checksum,
|
sortBy: AssetSort.checksum,
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async => remoteAssets);
|
).thenAnswer((_) async => remoteAssets);
|
||||||
@ -194,7 +197,7 @@ void main() {
|
|||||||
final currentState = [...remoteAssets];
|
final currentState = [...remoteAssets];
|
||||||
when(
|
when(
|
||||||
() => assetRepository.getAll(
|
() => assetRepository.getAll(
|
||||||
ownerId: owner.isarId,
|
ownerId: owner.id,
|
||||||
sortBy: AssetSort.checksum,
|
sortBy: AssetSort.checksum,
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async => currentState);
|
).thenAnswer((_) async => currentState);
|
||||||
@ -252,7 +255,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<(List<Asset>?, List<String>?)> _failDiff(
|
Future<(List<Asset>?, List<String>?)> _failDiff(
|
||||||
List<User> user,
|
List<UserDto> user,
|
||||||
DateTime time,
|
DateTime time,
|
||||||
) =>
|
) =>
|
||||||
Future.value((null, null));
|
Future.value((null, null));
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/album.interface.dart';
|
import 'package:immich_mobile/interfaces/album.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/album_api.interface.dart';
|
import 'package:immich_mobile/interfaces/album_api.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/album_media.interface.dart';
|
import 'package:immich_mobile/interfaces/album_media.interface.dart';
|
||||||
@ -9,7 +10,7 @@ import 'package:immich_mobile/interfaces/auth_api.interface.dart';
|
|||||||
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
|
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/file_media.interface.dart';
|
import 'package:immich_mobile/interfaces/file_media.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/user.interface.dart';
|
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockAlbumRepository extends Mock implements IAlbumRepository {}
|
class MockAlbumRepository extends Mock implements IAlbumRepository {}
|
||||||
@ -35,3 +36,5 @@ class MockAlbumApiRepository extends Mock implements IAlbumApiRepository {}
|
|||||||
class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
|
class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
|
||||||
|
|
||||||
class MockAuthRepository extends Mock implements IAuthRepository {}
|
class MockAuthRepository extends Mock implements IAuthRepository {}
|
||||||
|
|
||||||
|
class MockPartnerRepository extends Mock implements IPartnerRepository {}
|
||||||
|
@ -146,7 +146,7 @@ void main() {
|
|||||||
() => albumApiRepository.create(
|
() => albumApiRepository.create(
|
||||||
"name",
|
"name",
|
||||||
assetIds: [AssetStub.image1.remoteId!],
|
assetIds: [AssetStub.image1.remoteId!],
|
||||||
sharedUserIds: [UserStub.user1.id],
|
sharedUserIds: [UserStub.user1.uid],
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
verify(
|
verify(
|
||||||
@ -204,7 +204,7 @@ void main() {
|
|||||||
when(
|
when(
|
||||||
() => albumRepository.addUsers(
|
() => albumRepository.addUsers(
|
||||||
AlbumStub.emptyAlbum,
|
AlbumStub.emptyAlbum,
|
||||||
AlbumStub.emptyAlbum.sharedUsers.toList(),
|
AlbumStub.emptyAlbum.sharedUsers.map((u) => u.toDto()).toList(),
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async => AlbumStub.emptyAlbum);
|
).thenAnswer((_) async => AlbumStub.emptyAlbum);
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ void main() {
|
|||||||
|
|
||||||
final result = await sut.addUsers(
|
final result = await sut.addUsers(
|
||||||
AlbumStub.emptyAlbum,
|
AlbumStub.emptyAlbum,
|
||||||
[UserStub.user2.id],
|
[UserStub.user2.uid],
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result, true);
|
expect(result, true);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/services/entity.service.dart';
|
import 'package:immich_mobile/services/entity.service.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
import '../fixtures/asset.stub.dart';
|
import '../fixtures/asset.stub.dart';
|
||||||
import '../fixtures/user.stub.dart';
|
import '../fixtures/user.stub.dart';
|
||||||
import '../repository.mocks.dart';
|
import '../repository.mocks.dart';
|
||||||
@ -33,25 +35,32 @@ void main() {
|
|||||||
)
|
)
|
||||||
..remoteThumbnailAssetId = AssetStub.image1.remoteId
|
..remoteThumbnailAssetId = AssetStub.image1.remoteId
|
||||||
..assets.addAll([AssetStub.image1, AssetStub.image1])
|
..assets.addAll([AssetStub.image1, AssetStub.image1])
|
||||||
..owner.value = UserStub.user1
|
..owner.value = User.fromDto(UserStub.user1)
|
||||||
..sharedUsers.addAll([UserStub.admin, UserStub.admin]);
|
..sharedUsers.addAll(
|
||||||
|
[User.fromDto(UserStub.admin), User.fromDto(UserStub.admin)],
|
||||||
|
);
|
||||||
|
|
||||||
when(() => userRepository.get(album.ownerId!))
|
when(() => userRepository.get(any()))
|
||||||
|
.thenAnswer((_) async => UserStub.admin);
|
||||||
|
when(() => userRepository.getByUserId(any()))
|
||||||
.thenAnswer((_) async => UserStub.admin);
|
.thenAnswer((_) async => UserStub.admin);
|
||||||
|
|
||||||
when(() => assetRepository.getByRemoteId(AssetStub.image1.remoteId!))
|
when(() => assetRepository.getByRemoteId(AssetStub.image1.remoteId!))
|
||||||
.thenAnswer((_) async => AssetStub.image1);
|
.thenAnswer((_) async => AssetStub.image1);
|
||||||
|
|
||||||
when(() => userRepository.getByIds(any()))
|
when(() => userRepository.getByUserIds(any()))
|
||||||
.thenAnswer((_) async => [UserStub.user1, UserStub.user2]);
|
.thenAnswer((_) async => [UserStub.user1, UserStub.user2]);
|
||||||
|
|
||||||
when(() => assetRepository.getAllByRemoteId(any()))
|
when(() => assetRepository.getAllByRemoteId(any()))
|
||||||
.thenAnswer((_) async => [AssetStub.image1, AssetStub.image2]);
|
.thenAnswer((_) async => [AssetStub.image1, AssetStub.image2]);
|
||||||
|
|
||||||
await sut.fillAlbumWithDatabaseEntities(album);
|
await sut.fillAlbumWithDatabaseEntities(album);
|
||||||
expect(album.owner.value, UserStub.admin);
|
expect(album.owner.value?.toDto(), UserStub.admin);
|
||||||
expect(album.thumbnail.value, AssetStub.image1);
|
expect(album.thumbnail.value, AssetStub.image1);
|
||||||
expect(album.remoteUsers.toSet(), {UserStub.user1, UserStub.user2});
|
expect(
|
||||||
|
album.remoteUsers.map((u) => u.toDto()).toSet(),
|
||||||
|
{UserStub.user1, UserStub.user2},
|
||||||
|
);
|
||||||
expect(album.remoteAssets.toSet(), {AssetStub.image1, AssetStub.image2});
|
expect(album.remoteAssets.toSet(), {AssetStub.image1, AssetStub.image2});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
|
|||||||
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
|
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/etag.entity.dart';
|
import 'package:immich_mobile/entities/etag.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/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user