0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-11 02:23:09 -05:00

refactor: user entity

This commit is contained in:
shenlong-tanwen 2025-03-06 23:14:26 +05:30
parent a96bba4b26
commit d39e64040f
79 changed files with 881 additions and 823 deletions

View file

@ -67,7 +67,7 @@ custom_lint:
- lib/entities/*.entity.dart
- lib/repositories/{album,asset,backup,database,etag,exif_info,user,timeline,partner}.repository.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
# acceptable exceptions for the time being (until Isar is fully replaced)
- lib/providers/app_life_cycle.provider.dart
@ -93,6 +93,7 @@ custom_lint:
- lib/infrastructure/utils/*.converter.dart
# acceptable exceptions for the time being
- 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
- 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

View 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(User user);
Future<User?> get(int id);
Future<User?> getByUserId(String id);
Future<List<User?>> getByUserIds(List<String> ids);
Future<List<User>> getAll({SortUserBy? sortBy});
Future<bool> updateAll(List<User> users);
Future<User> update(User user);
Future<void> delete(List<int> ids);
Future<void> deleteAll();
}
enum SortUserBy { id }

View file

@ -1,4 +1,4 @@
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`.
/// Defines the data type for each value

View file

@ -0,0 +1,156 @@
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),
};
}
class User {
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 User({
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,
}''';
}
User 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,
}) =>
User(
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 User 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;
}

View file

@ -74,7 +74,7 @@ class StoreService {
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 {
if (_cache[key.id] == value) return;
await _storeRepository.insert(key, value);
@ -84,7 +84,7 @@ class StoreService {
/// Watches a specific key for changes
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 {
await _storeRepository.delete(key);
_cache.remove(key.id);

View file

@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/constants/enums.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:isar/isar.dart';
// ignore: implementation_imports

View file

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

View file

@ -1,8 +1,8 @@
import 'dart:typed_data';
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/user.entity.dart';
extension ListExtension<E> on List<E> {
List<E> uniqueConsecutive({
@ -62,7 +62,7 @@ extension AssetListExtension on Iterable<Asset> {
void Function()? errorCallback,
}) {
if (owner == null) return [];
final userId = owner.isarId;
final userId = owner.id;
final bool onlyOwned = every((e) => e.ownerId == userId);
if (!onlyOwned) {
if (errorCallback != null) errorCallback();

View file

@ -0,0 +1,74 @@
import 'package:immich_mobile/domain/models/user.model.dart' as model;
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 model.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 = model.AvatarColor.primary,
this.memoryEnabled = true,
this.inTimeline = false,
this.quotaUsageInBytes = 0,
this.quotaSizeInBytes = 0,
});
static User fromDto(model.User 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,
);
model.User toDto() => model.User(
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,
);
}

View file

@ -28,63 +28,58 @@ const UserSchema = CollectionSchema(
name: r'email',
type: IsarType.string,
),
r'hasQuota': PropertySchema(
id: 2,
name: r'hasQuota',
type: IsarType.bool,
),
r'id': PropertySchema(
id: 3,
id: 2,
name: r'id',
type: IsarType.string,
),
r'inTimeline': PropertySchema(
id: 4,
id: 3,
name: r'inTimeline',
type: IsarType.bool,
),
r'isAdmin': PropertySchema(
id: 5,
id: 4,
name: r'isAdmin',
type: IsarType.bool,
),
r'isPartnerSharedBy': PropertySchema(
id: 6,
id: 5,
name: r'isPartnerSharedBy',
type: IsarType.bool,
),
r'isPartnerSharedWith': PropertySchema(
id: 7,
id: 6,
name: r'isPartnerSharedWith',
type: IsarType.bool,
),
r'memoryEnabled': PropertySchema(
id: 8,
id: 7,
name: r'memoryEnabled',
type: IsarType.bool,
),
r'name': PropertySchema(
id: 9,
id: 8,
name: r'name',
type: IsarType.string,
),
r'profileImagePath': PropertySchema(
id: 10,
id: 9,
name: r'profileImagePath',
type: IsarType.string,
),
r'quotaSizeInBytes': PropertySchema(
id: 11,
id: 10,
name: r'quotaSizeInBytes',
type: IsarType.long,
),
r'quotaUsageInBytes': PropertySchema(
id: 12,
id: 11,
name: r'quotaUsageInBytes',
type: IsarType.long,
),
r'updatedAt': PropertySchema(
id: 13,
id: 12,
name: r'updatedAt',
type: IsarType.dateTime,
)
@ -109,22 +104,7 @@ const UserSchema = CollectionSchema(
],
)
},
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',
)
},
links: {},
embeddedSchemas: {},
getId: _userGetId,
getLinks: _userGetLinks,
@ -153,18 +133,17 @@ void _userSerialize(
) {
writer.writeByte(offsets[0], object.avatarColor.index);
writer.writeString(offsets[1], object.email);
writer.writeBool(offsets[2], object.hasQuota);
writer.writeString(offsets[3], object.id);
writer.writeBool(offsets[4], object.inTimeline);
writer.writeBool(offsets[5], object.isAdmin);
writer.writeBool(offsets[6], object.isPartnerSharedBy);
writer.writeBool(offsets[7], object.isPartnerSharedWith);
writer.writeBool(offsets[8], object.memoryEnabled);
writer.writeString(offsets[9], object.name);
writer.writeString(offsets[10], object.profileImagePath);
writer.writeLong(offsets[11], object.quotaSizeInBytes);
writer.writeLong(offsets[12], object.quotaUsageInBytes);
writer.writeDateTime(offsets[13], object.updatedAt);
writer.writeString(offsets[2], object.id);
writer.writeBool(offsets[3], object.inTimeline);
writer.writeBool(offsets[4], object.isAdmin);
writer.writeBool(offsets[5], object.isPartnerSharedBy);
writer.writeBool(offsets[6], object.isPartnerSharedWith);
writer.writeBool(offsets[7], object.memoryEnabled);
writer.writeString(offsets[8], object.name);
writer.writeString(offsets[9], object.profileImagePath);
writer.writeLong(offsets[10], object.quotaSizeInBytes);
writer.writeLong(offsets[11], object.quotaUsageInBytes);
writer.writeDateTime(offsets[12], object.updatedAt);
}
User _userDeserialize(
@ -176,19 +155,19 @@ User _userDeserialize(
final object = User(
avatarColor:
_UseravatarColorValueEnumMap[reader.readByteOrNull(offsets[0])] ??
AvatarColorEnum.primary,
model.AvatarColor.primary,
email: reader.readString(offsets[1]),
id: reader.readString(offsets[3]),
inTimeline: reader.readBoolOrNull(offsets[4]) ?? false,
isAdmin: reader.readBool(offsets[5]),
isPartnerSharedBy: reader.readBoolOrNull(offsets[6]) ?? false,
isPartnerSharedWith: reader.readBoolOrNull(offsets[7]) ?? false,
memoryEnabled: reader.readBoolOrNull(offsets[8]) ?? true,
name: reader.readString(offsets[9]),
profileImagePath: reader.readStringOrNull(offsets[10]) ?? '',
quotaSizeInBytes: reader.readLongOrNull(offsets[11]) ?? 0,
quotaUsageInBytes: reader.readLongOrNull(offsets[12]) ?? 0,
updatedAt: reader.readDateTime(offsets[13]),
id: reader.readString(offsets[2]),
inTimeline: reader.readBoolOrNull(offsets[3]) ?? false,
isAdmin: reader.readBool(offsets[4]),
isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false,
isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false,
memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true,
name: reader.readString(offsets[8]),
profileImagePath: reader.readStringOrNull(offsets[9]) ?? '',
quotaSizeInBytes: reader.readLongOrNull(offsets[10]) ?? 0,
quotaUsageInBytes: reader.readLongOrNull(offsets[11]) ?? 0,
updatedAt: reader.readDateTime(offsets[12]),
);
return object;
}
@ -202,32 +181,30 @@ P _userDeserializeProp<P>(
switch (propertyId) {
case 0:
return (_UseravatarColorValueEnumMap[reader.readByteOrNull(offset)] ??
AvatarColorEnum.primary) as P;
model.AvatarColor.primary) as P;
case 1:
return (reader.readString(offset)) as P;
case 2:
return (reader.readBool(offset)) as P;
case 3:
return (reader.readString(offset)) as P;
case 4:
case 3:
return (reader.readBoolOrNull(offset) ?? false) as P;
case 5:
case 4:
return (reader.readBool(offset)) as P;
case 5:
return (reader.readBoolOrNull(offset) ?? false) as P;
case 6:
return (reader.readBoolOrNull(offset) ?? false) as P;
case 7:
return (reader.readBoolOrNull(offset) ?? false) as P;
case 8:
return (reader.readBoolOrNull(offset) ?? true) as P;
case 9:
case 8:
return (reader.readString(offset)) as P;
case 10:
case 9:
return (reader.readStringOrNull(offset) ?? '') as P;
case 10:
return (reader.readLongOrNull(offset) ?? 0) as P;
case 11:
return (reader.readLongOrNull(offset) ?? 0) as P;
case 12:
return (reader.readLongOrNull(offset) ?? 0) as P;
case 13:
return (reader.readDateTime(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
@ -247,16 +224,16 @@ const _UseravatarColorEnumValueMap = {
'amber': 9,
};
const _UseravatarColorValueEnumMap = {
0: AvatarColorEnum.primary,
1: AvatarColorEnum.pink,
2: AvatarColorEnum.red,
3: AvatarColorEnum.yellow,
4: AvatarColorEnum.blue,
5: AvatarColorEnum.green,
6: AvatarColorEnum.purple,
7: AvatarColorEnum.orange,
8: AvatarColorEnum.gray,
9: AvatarColorEnum.amber,
0: AvatarColor.primary,
1: AvatarColor.pink,
2: AvatarColor.red,
3: AvatarColor.yellow,
4: AvatarColor.blue,
5: AvatarColor.green,
6: AvatarColor.purple,
7: AvatarColor.orange,
8: AvatarColor.gray,
9: AvatarColor.amber,
};
Id _userGetId(User object) {
@ -264,14 +241,10 @@ Id _userGetId(User object) {
}
List<IsarLinkBase<dynamic>> _userGetLinks(User object) {
return [object.albums, object.sharedAlbums];
return [];
}
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);
}
void _userAttach(IsarCollection<dynamic> col, Id id, User object) {}
extension UserByIndex on IsarCollection<User> {
Future<User?> getById(String id) {
@ -447,7 +420,7 @@ extension UserQueryWhere on QueryBuilder<User, User, QWhereClause> {
extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
QueryBuilder<User, User, QAfterFilterCondition> avatarColorEqualTo(
AvatarColorEnum value) {
AvatarColor value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'avatarColor',
@ -457,7 +430,7 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
}
QueryBuilder<User, User, QAfterFilterCondition> avatarColorGreaterThan(
AvatarColorEnum value, {
AvatarColor value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
@ -470,7 +443,7 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
}
QueryBuilder<User, User, QAfterFilterCondition> avatarColorLessThan(
AvatarColorEnum value, {
AvatarColor value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
@ -483,8 +456,8 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
}
QueryBuilder<User, User, QAfterFilterCondition> avatarColorBetween(
AvatarColorEnum lower,
AvatarColorEnum upper, {
AvatarColor lower,
AvatarColor upper, {
bool includeLower = 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(
String value, {
bool caseSensitive = true,
@ -1285,118 +1249,7 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
extension UserQueryObject 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 UserQueryLinks on QueryBuilder<User, User, QFilterCondition> {}
extension UserQuerySortBy on QueryBuilder<User, User, QSortBy> {
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() {
return QueryBuilder.apply(this, (query) {
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() {
return QueryBuilder.apply(this, (query) {
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(
{bool caseSensitive = true}) {
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 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() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');

View file

@ -1,9 +1,9 @@
import 'package:immich_mobile/domain/interfaces/store.interface.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/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';
class IsarStoreRepository extends IsarDatabaseRepository
@ -78,7 +78,7 @@ class IsarStoreRepository extends IsarDatabaseRepository
const (DateTime) => entity.intValue == null
? null
: DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
const (User) => await UserRepository(_db).getByDbId(entity.intValue!),
const (User) => await IsarUserRepository(_db).get(entity.intValue!),
_ => null,
} as T?;
@ -89,7 +89,7 @@ class IsarStoreRepository extends IsarDatabaseRepository
const (bool) => ((value as bool) ? 1 : 0, null),
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
const (User) => (
(await UserRepository(_db).update(value as User)).isarId,
(await IsarUserRepository(_db).update(value as User)).id,
null,
),
_ => throw UnsupportedError(

View 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<User?> get(int id) async {
return (await _db.users.get(id))?.toDto();
}
@override
Future<List<User>> 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<User?> getByUserId(String id) async {
return (await _db.users.getById(id))?.toDto();
}
@override
Future<List<User?>> getByUserIds(List<String> ids) async {
return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList();
}
@override
Future<bool> insert(User user) async {
await transaction(() async {
await _db.users.put(entity.User.fromDto(user));
});
return true;
}
@override
Future<User> update(User user) async {
await transaction(() async {
await _db.users.put(entity.User.fromDto(user));
});
return user;
}
@override
Future<bool> updateAll(List<User> users) async {
await transaction(() async {
await _db.users.putAll(users.map(entity.User.fromDto).toList());
});
return true;
}
}

View 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 User fromSimpleUserDto(UserResponseDto dto) => User(
uid: dto.id,
email: dto.email,
name: dto.name,
isAdmin: false,
updatedAt: DateTime.now(),
profileImagePath: dto.profileImagePath,
avatarColor: dto.avatarColor.toAvatarColor(),
);
static User fromAdminDto(
UserAdminResponseDto adminDto, [
UserPreferencesResponseDto? preferenceDto,
]) =>
User(
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 User fromPartnerDto(PartnerResponseDto dto) => User(
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,
};
}

View file

@ -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/asset.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/interfaces/database.interface.dart';
import 'package:immich_mobile/models/albums/album_search.model.dart';

View file

@ -1,4 +1,4 @@
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract class IPartnerRepository {
Future<List<User>> getSharedWith();

View file

@ -1,4 +1,4 @@
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IPartnerApiRepository {
Future<List<User>> getAll(Direction direction);

View file

@ -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 }

View file

@ -1,6 +1,6 @@
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 {
Future<List<User>> getAll();

View file

@ -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 }

View file

@ -3,11 +3,11 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/build_context_extensions.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';
@RoutePage()
@ -151,7 +151,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
onData: (users) {
for (var sharedUsers in album.sharedUsers) {
users.removeWhere(
(u) => u.id == sharedUsers.id || u.id == album.ownerId,
(u) => u.uid == sharedUsers.id || u.uid == album.ownerId,
);
}

View file

@ -4,14 +4,16 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.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/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/current_album.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/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/user_circle_avatar.dart';
@ -26,7 +28,8 @@ class AlbumOptionsPage extends HookConsumerWidget {
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 userId = ref.watch(authProvider).userId;
final activityEnabled = useState(album.activityEnabled);
@ -69,8 +72,8 @@ class AlbumOptionsPage extends HookConsumerWidget {
try {
await ref.read(albumProvider.notifier).removeUser(album, user);
album.sharedUsers.remove(user);
sharedUsers.value = album.sharedUsers.toList();
album.sharedUsers.remove(entity.User.fromDto(user));
sharedUsers.value = album.sharedUsers.map((u) => u.toDto()).toList();
} catch (error) {
showErrorMessage();
}
@ -82,7 +85,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
void handleUserClick(User user) {
var actions = [];
if (user.id == userId) {
if (user.uid == userId) {
actions = [
ListTile(
leading: const Icon(Icons.exit_to_app_rounded),
@ -123,8 +126,9 @@ class AlbumOptionsPage extends HookConsumerWidget {
buildOwnerInfo() {
return ListTile(
leading:
owner != null ? UserCircleAvatar(user: owner) : const SizedBox(),
leading: owner != null
? UserCircleAvatar(user: owner.toDto())
: const SizedBox(),
title: Text(
album.owner.value?.name ?? "",
style: const TextStyle(
@ -166,10 +170,10 @@ class AlbumOptionsPage extends HookConsumerWidget {
color: context.colorScheme.onSurfaceSecondary,
),
),
trailing: userId == user.id || isOwner
trailing: userId == user.uid || isOwner
? const Icon(Icons.more_horiz_rounded)
: const SizedBox(),
onTap: userId == user.id || isOwner
onTap: userId == user.uid || isOwner
? () => handleUserClick(user)
: null,
);

View file

@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/routing/router.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
@ -23,7 +23,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget {
return sharedUsers.value;
}
return album.sharedUsers.toList(growable: false);
return album.sharedUsers.map((u) => u.toDto()).toList(growable: false);
}),
);

View file

@ -3,14 +3,14 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/build_context_extensions.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/suggested_shared_users.provider.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';
@RoutePage()

View file

@ -33,7 +33,7 @@ class AlbumsPage extends HookConsumerWidget {
final searchController = useTextEditingController();
final debounceTimer = useRef<Timer?>(null);
final filterMode = useState(QuickFilterMode.all);
final userId = ref.watch(currentUserProvider)?.id;
final userId = ref.watch(currentUserProvider)?.uid;
final searchFocusNode = useFocusNode();
toggleViewMode() {

View file

@ -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/models/activities/activity.model.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/asset_viewer/current_asset.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()
class ActivitiesPage extends HookConsumerWidget {
@ -72,7 +72,7 @@ class ActivitiesPage extends HookConsumerWidget {
final activity = data[index];
final canDelete = activity.user.id == user?.id ||
album.ownerId == user?.id;
album.ownerId == user?.uid;
return Padding(
padding: const EdgeInsets.all(5),

View file

@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/build_context_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';

View file

@ -2,10 +2,10 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/providers/partner.provider.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/immich_toast.dart';
import 'package:immich_mobile/widgets/common/user_avatar.dart';

View file

@ -2,11 +2,11 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/providers/asset.provider.dart';
import 'package:immich_mobile/providers/multiselect.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/widgets/asset_grid/multiselect_grid.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
@ -111,7 +111,7 @@ class PartnerDetailPage extends HookConsumerWidget {
),
),
),
renderListProvider: singleUserTimelineProvider(partner.isarId),
renderListProvider: singleUserTimelineProvider(partner.id),
onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(),
deleteEnabled: false,
favoriteEnabled: false,

View file

@ -7,16 +7,16 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.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/multiselect.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/websocket.provider.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_loading_indicator.dart';
import 'package:immich_mobile/widgets/memories/memory_lane.dart';
@RoutePage()
class PhotosPage extends HookConsumerWidget {
@ -110,7 +110,7 @@ class PhotosPage extends HookConsumerWidget {
: const SizedBox(),
renderListProvider: timelineUsers.length > 1
? multiUsersTimelineProvider(timelineUsers)
: singleUserTimelineProvider(currentUser?.isarId),
: singleUserTimelineProvider(currentUser?.id),
buildLoadingIndicator: buildLoadingIndicator,
onRefresh: refreshAssets,
stackEnabled: true,

View file

@ -2,11 +2,11 @@ import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.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/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);

View file

@ -1,9 +1,13 @@
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';
final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) {
final otherUsersProvider = FutureProvider.autoDispose<List<User>>((ref) async {
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;
});

View file

@ -2,8 +2,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_udid/flutter_udid.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/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/login_response.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
@ -141,18 +142,18 @@ class AuthNotifier extends StateNotifier<AuthState> {
// If the user information is successfully retrieved, update the store
// 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.deviceIdHash, fastHash(deviceId));
await Store.put(
StoreKey.currentUser,
User.fromUserDto(userResponse, userPreferences),
UserConverter.fromAdminDto(userResponse, userPreferences),
);
await Store.put(StoreKey.accessToken, accessToken);
user = User.fromUserDto(userResponse, userPreferences);
} else {
_log.severe("Unable to get user information from the server.");
user = UserConverter.fromAdminDto(userResponse, userPreferences);
}
// If the user is null, the login was not successful
@ -163,7 +164,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
state = state.copyWith(
isAuthenticated: true,
userId: user.id,
userId: user.uid,
userEmail: user.email,
name: user.name,
profileImagePath: user.profileImagePath,

View file

@ -1,11 +1,14 @@
import 'package:hooks_riverpod/hooks_riverpod.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/providers/infrastructure/db.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'store.provider.g.dart';
@riverpod
IStoreRepository storeRepository(Ref ref) =>
@Riverpod(keepAlive: true)
IStoreRepository storeRepository(StoreRepositoryRef ref) =>
IsarStoreRepository(ref.watch(isarProvider));
@Riverpod(keepAlive: true)
StoreService storeService(StoreServiceRef _) => StoreService.I;

View file

@ -6,11 +6,11 @@ part of 'store.provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$storeRepositoryHash() => r'2f1c3e2e2db5082a40eb30a183a6c770f5b09d76';
String _$storeRepositoryHash() => r'9bf5fb8aebbe4439078d6507671efa900e8f13b7';
/// See also [storeRepository].
@ProviderFor(storeRepository)
final storeRepositoryProvider = AutoDisposeProvider<IStoreRepository>.internal(
final storeRepositoryProvider = Provider<IStoreRepository>.internal(
storeRepository,
name: r'storeRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@ -20,8 +20,20 @@ final storeRepositoryProvider = AutoDisposeProvider<IStoreRepository>.internal(
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef StoreRepositoryRef = AutoDisposeProviderRef<IStoreRepository>;
typedef StoreRepositoryRef = ProviderRef<IStoreRepository>;
String _$storeServiceHash() => r'a072a39c42ad1a4d1133706f408c89c2415ba35e';
/// 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,
);
typedef StoreServiceRef = ProviderRef<StoreService>;
// 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

View file

@ -0,0 +1,10 @@
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(UserRepositoryRef ref) =>
IsarUserRepository(ref.watch(isarProvider));

View file

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$userRepositoryHash() => r'17ad0964faccd58f1e2571452055e74613f6da18';
/// 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,
);
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

View file

@ -2,9 +2,9 @@ import 'dart:async';
import 'package:collection/collection.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/services/partner.service.dart';
import 'package:immich_mobile/entities/user.entity.dart';
class PartnerSharedWithNotifier extends StateNotifier<List<User>> {
final PartnerService _partnerService;

View file

@ -2,8 +2,9 @@ import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.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/user.entity.dart';
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/timeline.service.dart';
@ -25,7 +26,7 @@ class CurrentUserProvider extends StateNotifier<User?> {
if (user != null) {
await Store.put(
StoreKey.currentUser,
User.fromUserDto(user, userPreferences),
UserConverter.fromAdminDto(user, userPreferences),
);
}
} catch (_) {}

View file

@ -1,5 +1,5 @@
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/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
@ -60,7 +60,7 @@ class ActivityApiRepository extends ApiRepository
type: dto.type == ReactionType.comment
? ActivityType.comment
: ActivityType.like,
user: User.fromSimpleUserDto(dto.user),
user: UserConverter.fromSimpleUserDto(dto.user),
assetId: dto.assetId,
comment: dto.comment,
);

View file

@ -1,9 +1,11 @@
import 'package:hooks_riverpod/hooks_riverpod.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/asset.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/models/albums/album_search.model.dart';
import 'package:immich_mobile/providers/db.provider.dart';
@ -43,11 +45,11 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
}
if (owner == true) {
query = query.owner(
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id),
);
} else if (owner == false) {
query = query.owner(
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id),
);
}
if (remote == true) {
@ -100,8 +102,9 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
Future<Album?> get(int id) => db.albums.get(id);
@override
Future<void> removeUsers(Album album, List<User> users) =>
txn(() => album.sharedUsers.update(unlink: users));
Future<void> removeUsers(Album album, List<User> users) => txn(
() => album.sharedUsers.update(unlink: users.map(entity.User.fromDto)),
);
@override
Future<void> addAssets(Album album, List<Asset> assets) =>
@ -122,7 +125,7 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
@override
Future<void> addUsers(Album album, List<User> users) =>
txn(() => album.sharedUsers.update(link: users));
txn(() => album.sharedUsers.update(link: users.map(entity.User.fromDto)));
@override
Future<void> deleteAllLocal() =>
@ -141,11 +144,11 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
switch (filterMode) {
case QuickFilterMode.sharedWithMe:
query = query.owner(
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id),
);
case QuickFilterMode.myAlbums:
query = query.owner(
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id),
);
case QuickFilterMode.all:
break;

View file

@ -2,7 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/album.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/providers/api.provider.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,
);
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;
final users = dto.albumUsers
.map((albumUser) => User.fromSimpleUserDto(albumUser.user));
album.sharedUsers.addAll(users);
.map((albumUser) => UserConverter.fromSimpleUserDto(albumUser.user));
album.sharedUsers.addAll(users.map(entity.User.fromDto));
final assets = dto.assets.map(Asset.remote).toList();
album.assets.addAll(assets);
return album;

View file

@ -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/asset.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/repositories/asset_media.repository.dart';
import 'package:photo_manager/photo_manager.dart' hide AssetType;
@ -86,7 +87,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
shared: false,
activityEnabled: false,
);
album.owner.value = Store.get(StoreKey.currentUser);
album.owner.value = User.fromDto(Store.get(StoreKey.currentUser));
album.localId = assetPathEntity.id;
album.isAll = assetPathEntity.isAll;
return album;

View file

@ -24,7 +24,7 @@ class AssetMediaRepository implements IAssetMediaRepository {
final Asset asset = Asset(
checksum: "",
localId: local.id,
ownerId: Store.get(StoreKey.currentUser).isarId,
ownerId: Store.get(StoreKey.currentUser).id,
fileCreatedAt: local.createDateTime,
fileModifiedAt: local.modifiedDateTime,
updatedAt: local.modifiedDateTime,

View file

@ -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/etag.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/user.entity.dart';
import 'package:immich_mobile/interfaces/auth.interface.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/providers/db.provider.dart';

View file

@ -1,5 +1,7 @@
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/providers/db.provider.dart';
import 'package:immich_mobile/repositories/database.repository.dart';
@ -14,34 +16,40 @@ class PartnerRepository extends DatabaseRepository
PartnerRepository(super.db);
@override
Future<List<User>> getSharedBy() {
return db.users
.filter()
.isPartnerSharedByEqualTo(true)
.sortById()
.findAll();
Future<List<User>> getSharedBy() async {
return (await db.users
.filter()
.isPartnerSharedByEqualTo(true)
.sortById()
.findAll())
.map((u) => u.toDto())
.toList();
}
@override
Future<List<User>> getSharedWith() {
return db.users
.filter()
.isPartnerSharedWithEqualTo(true)
.sortById()
.findAll();
Future<List<User>> getSharedWith() async {
return (await db.users
.filter()
.isPartnerSharedWithEqualTo(true)
.sortById()
.findAll())
.map((u) => u.toDto())
.toList();
}
@override
Stream<List<User>> 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
Stream<List<User>> watchSharedWith() {
return db.users
.filter()
.isPartnerSharedWithEqualTo(true)
.sortById()
.watch();
return (db.users
.filter()
.isPartnerSharedWithEqualTo(true)
.sortById()
.watch())
.map((users) => users.map((u) => u.toDto()).toList());
}
}

View file

@ -1,5 +1,6 @@
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/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart';
@ -26,13 +27,13 @@ class PartnerApiRepository extends ApiRepository
: PartnerDirection.with_,
),
);
return response.map(User.fromPartnerDto).toList();
return response.map(UserConverter.fromPartnerDto).toList();
}
@override
Future<User> create(String id) async {
final dto = await checkNull(_api.createPartner(id));
return User.fromPartnerDto(dto);
return UserConverter.fromPartnerDto(dto);
}
@override
@ -46,6 +47,6 @@ class PartnerApiRepository extends ApiRepository
UpdatePartnerDto(inTimeline: inTimeline),
),
);
return User.fromPartnerDto(dto);
return UserConverter.fromPartnerDto(dto);
}
}

View file

@ -2,7 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/album.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/providers/db.provider.dart';
import 'package:immich_mobile/repositories/database.repository.dart';

View file

@ -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();
});
}
}

View file

@ -2,7 +2,8 @@ import 'dart:typed_data';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/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/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart';
@ -22,7 +23,7 @@ class UserApiRepository extends ApiRepository implements IUserApiRepository {
@override
Future<List<User>> getAll() async {
final dto = await checkNull(_api.searchUsers());
return dto.map(User.fromSimpleUserDto).toList();
return dto.map(UserConverter.fromSimpleUserDto).toList();
}
@override

View file

@ -2,11 +2,10 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/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/pages/library/folder/folder.page.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/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/library/archive.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/local_albums.page.dart';
import 'package:immich_mobile/pages/library/partner/partner.page.dart';

View file

@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
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/infrastructure/utils/user.converter.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/memory.provider.dart';
@ -39,7 +39,7 @@ class TabNavigationObserver extends AutoRouterObserver {
await Store.put(
StoreKey.currentUser,
User.fromUserDto(userResponseDto, userPreferences),
UserConverter.fromAdminDto(userResponseDto, userPreferences),
);
ref.read(serverInfoProvider.notifier).getServerVersion();
} catch (e) {

View file

@ -7,11 +7,13 @@ import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.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/asset.entity.dart';
import 'package:immich_mobile/entities/backup_album.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_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
@ -207,7 +209,7 @@ class AlbumService {
final Album album = await _albumApiRepository.create(
albumName,
assetIds: assets.map((asset) => asset.remoteId!),
sharedUserIds: sharedUsers.map((user) => user.id),
sharedUserIds: sharedUsers.map((user) => user.uid),
);
await _entityService.fillAlbumWithDatabaseEntities(album);
return _albumRepository.create(album);
@ -294,7 +296,7 @@ class AlbumService {
Future<bool> deleteAlbum(Album album) async {
try {
final userId = Store.get(StoreKey.currentUser).isarId;
final userId = Store.get(StoreKey.currentUser).id;
if (album.owner.value?.isarId == userId) {
await _albumApiRepository.delete(album.remoteId!);
}
@ -361,10 +363,10 @@ class AlbumService {
try {
await _albumApiRepository.removeUser(
album.remoteId!,
userId: user.id,
userId: user.uid,
);
album.sharedUsers.remove(user);
album.sharedUsers.remove(entity.User.fromDto(user));
await _albumRepository.removeUsers(album, [user]);
final a = await _albumRepository.get(album.id);
// trigger watcher
@ -388,7 +390,10 @@ class AlbumService {
album.sharedUsers.addAll(updatedAlbum.remoteUsers);
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);
return true;

View file

@ -5,24 +5,27 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/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_api.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/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/providers/api.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_api.repository.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/repositories/backup.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/api.service.dart';
import 'package:immich_mobile/services/backup.service.dart';
@ -45,6 +48,7 @@ final assetServiceProvider = Provider(
ref.watch(userServiceProvider),
ref.watch(backupServiceProvider),
ref.watch(albumServiceProvider),
ref.watch(storeServiceProvider),
ref.watch(assetMediaRepositoryProvider),
),
);
@ -61,6 +65,7 @@ class AssetService {
final UserService _userService;
final BackupService _backupService;
final AlbumService _albumService;
final StoreService _storeService;
final IAssetMediaRepository _assetMediaRepository;
final log = Logger('AssetService');
@ -76,6 +81,7 @@ class AssetService {
this._userService,
this._backupService,
this._albumService,
this._storeService,
this._assetMediaRepository,
);
@ -85,7 +91,7 @@ class AssetService {
final syncedUserIds = await _etagRepository.getAllIds();
final List<User> syncedUsers = syncedUserIds.isEmpty
? []
: await _userRepository.getByIds(syncedUserIds);
: (await _userRepository.getByUserIds(syncedUserIds)).nonNulls.toList();
final Stopwatch sw = Stopwatch()..start();
final bool changes = await _syncService.syncRemoteAssetsToDb(
users: syncedUsers,
@ -102,7 +108,7 @@ class AssetService {
_getRemoteAssetChanges(List<User> users, DateTime since) async {
final dto = AssetDeltaSyncDto(
updatedAfter: since,
userIds: users.map((e) => e.id).toList(),
userIds: users.map((e) => e.uid).toList(),
);
final changes = await _apiService.syncApi.getDeltaSync(dto);
return changes == null || changes.needsFullSync
@ -143,7 +149,7 @@ class AssetService {
limit: chunkSize,
updatedUntil: until,
lastId: lastId,
userId: user.id,
userId: user.uid,
);
log.fine("Requesting $chunkSize assets from $lastId");
final List<AssetResponseDto>? assets =
@ -314,9 +320,9 @@ class AssetService {
);
await refreshRemoteAssets();
final owner = await _userRepository.me();
final owner = _storeService.get(StoreKey.currentUser);
final remoteAssets = await _assetRepository.getAll(
ownerId: owner.isarId,
ownerId: owner.id,
state: AssetState.merged,
);
@ -519,13 +525,13 @@ class AssetService {
return _assetRepository.watchAsset(id, fireImmediately: fireImmediately);
}
Future<List<Asset>> getRecentlyAddedAssets() async {
final me = await _userRepository.me();
return _assetRepository.getRecentlyAddedAssets(me.isarId);
Future<List<Asset>> getRecentlyAddedAssets() {
final me = _storeService.get(StoreKey.currentUser);
return _assetRepository.getRecentlyAddedAssets(me.id);
}
Future<List<Asset>> getMotionAssets() async {
final me = await _userRepository.me();
return _assetRepository.getMotionAssets(me.isarId);
Future<List<Asset>> getMotionAssets() {
final me = _storeService.get(StoreKey.currentUser);
return _assetRepository.getMotionAssets(me.id);
}
}

View file

@ -12,10 +12,13 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/services/store.service.dart';
import 'package:immich_mobile/entities/backup_album.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/user.repository.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
@ -34,7 +37,6 @@ import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/repositories/network.repository.dart';
import 'package:immich_mobile/repositories/partner_api.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/services/album.service.dart';
import 'package:immich_mobile/services/api.service.dart';
@ -385,7 +387,7 @@ class BackgroundService {
AlbumMediaRepository albumMediaRepository = AlbumMediaRepository();
FileMediaRepository fileMediaRepository = FileMediaRepository();
AssetMediaRepository assetMediaRepository = AssetMediaRepository();
UserRepository userRepository = UserRepository(db);
IUserRepository userRepository = IsarUserRepository(db);
UserApiRepository userApiRepository =
UserApiRepository(apiService.usersApi);
AlbumApiRepository albumApiRepository =
@ -405,6 +407,7 @@ class BackgroundService {
assetRepository,
exifInfoRepository,
userRepository,
StoreService.I,
eTagRepository,
);
UserService userService = UserService(

View file

@ -34,7 +34,7 @@ class BackupVerificationService {
/// Returns at most [limit] assets that were backed up without exif
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(
ownerId: owner,
state: AssetState.local,

View file

@ -1,9 +1,10 @@
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/infrastructure/entities/user.entity.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/user.repository.dart';
class EntityService {
final IAssetRepository _assetRepository;
@ -17,7 +18,8 @@ class EntityService {
final ownerId = album.ownerId;
if (ownerId != null) {
// 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 =
album.remoteThumbnailAssetId ?? album.thumbnail.value?.remoteId;
@ -29,9 +31,9 @@ class EntityService {
if (album.remoteUsers.isNotEmpty) {
// replace all users with users from database
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.addAll(users);
album.sharedUsers.addAll(users.nonNulls.map(User.fromDto));
album.shared = true;
}
if (album.remoteAssets.isNotEmpty) {

View file

@ -1,11 +1,11 @@
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_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_api.repository.dart';
import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:logging/logging.dart';
final partnerServiceProvider = Provider(
@ -46,11 +46,10 @@ class PartnerService {
Future<bool> removePartner(User partner) async {
try {
await _partnerApiRepository.delete(partner.id);
partner.isPartnerSharedBy = false;
await _userRepository.update(partner);
await _partnerApiRepository.delete(partner.uid);
await _userRepository.update(partner.copyWith(isPartnerSharedBy: false));
} catch (e) {
_log.warning("Failed to remove partner ${partner.id}", e);
_log.warning("Failed to remove partner ${partner.uid}", e);
return false;
}
return true;
@ -58,12 +57,11 @@ class PartnerService {
Future<bool> addPartner(User partner) async {
try {
await _partnerApiRepository.create(partner.id);
partner.isPartnerSharedBy = true;
await _userRepository.update(partner);
await _partnerApiRepository.create(partner.uid);
await _userRepository.update(partner.copyWith(isPartnerSharedBy: true));
return true;
} catch (e) {
_log.warning("Failed to add partner ${partner.id}", e);
_log.warning("Failed to add partner ${partner.uid}", e);
}
return false;
}
@ -71,14 +69,14 @@ class PartnerService {
Future<bool> updatePartner(User partner, {required bool inTimeline}) async {
try {
final dto = await _partnerApiRepository.update(
partner.id,
partner.uid,
inTimeline: inTimeline,
);
partner.inTimeline = dto.inTimeline;
await _userRepository.update(partner);
await _userRepository
.update(partner.copyWith(inTimeline: dto.inTimeline));
return true;
} catch (e) {
_log.warning("Failed to update partner ${partner.id}", e);
_log.warning("Failed to update partner ${partner.uid}", e);
}
return false;
}

View file

@ -3,24 +3,27 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/asset.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/interfaces/album.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/asset.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/user.interface.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_api.repository.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/etag.repository.dart';
import 'package:immich_mobile/repositories/user.repository.dart';
import 'package:immich_mobile/services/entity.service.dart';
import 'package:immich_mobile/services/hash.service.dart';
import 'package:immich_mobile/utils/async_mutex.dart';
@ -38,6 +41,7 @@ final syncServiceProvider = Provider(
ref.watch(assetRepositoryProvider),
ref.watch(exifRepositoryProvider),
ref.watch(userRepositoryProvider),
ref.watch(storeServiceProvider),
ref.watch(etagRepositoryProvider),
),
);
@ -51,6 +55,7 @@ class SyncService {
final IAssetRepository _assetRepository;
final IExifInfoRepository _exifInfoRepository;
final IUserRepository _userRepository;
final StoreService _storeService;
final IETagRepository _eTagRepository;
final AsyncMutex _lock = AsyncMutex();
final Logger _log = Logger('SyncService');
@ -64,6 +69,7 @@ class SyncService {
this._assetRepository,
this._exifInfoRepository,
this._userRepository,
this._storeService,
this._eTagRepository,
);
@ -135,8 +141,8 @@ class SyncService {
/// Syncs users from the server to the local database
/// Returns `true`if there were any changes
Future<bool> _syncUsersFromServer(List<User> users) async {
users.sortBy((u) => u.id);
final dbUsers = await _userRepository.getAll(sortBy: UserSort.id);
users.sortBy((u) => u.uid);
final dbUsers = await _userRepository.getAll(sortBy: SortUserBy.id);
final List<int> toDelete = [];
final List<User> toUpsert = [];
final changes = diffSortedListsSync(
@ -154,12 +160,12 @@ class SyncService {
return false;
},
onlyFirst: (User a) => toUpsert.add(a),
onlySecond: (User b) => toDelete.add(b.isarId),
onlySecond: (User b) => toDelete.add(b.id),
);
if (changes) {
await _userRepository.transaction(() async {
await _userRepository.deleteById(toDelete);
await _userRepository.upsertAll(toUpsert);
await _userRepository.delete(toDelete);
await _userRepository.updateAll(toUpsert);
});
}
return changes;
@ -191,9 +197,9 @@ class SyncService {
DateTime since,
) getChangedAssets,
) async {
final currentUser = await _userRepository.me();
final currentUser = _storeService.get(StoreKey.currentUser);
final DateTime? since =
(await _eTagRepository.get(currentUser.isarId))?.time?.toUtc();
(await _eTagRepository.get(currentUser.id))?.time?.toUtc();
if (since == null) return null;
final DateTime now = DateTime.now();
final (toUpsert, toDelete) = await getChangedAssets(users, since);
@ -251,7 +257,7 @@ class SyncService {
return false;
}
await _syncUsersFromServer(serverUsers);
final List<User> users = await _userRepository.getAllAccessible();
final List<User> users = await _userRepository.getAll();
bool changes = false;
for (User u in users) {
changes |= await _syncRemoteAssetsForUser(u, loadAssets);
@ -269,7 +275,7 @@ class SyncService {
return false;
}
final List<Asset> inDb = await _assetRepository.getAll(
ownerId: user.isarId,
ownerId: user.id,
sortBy: AssetSort.checksum,
);
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
@ -296,12 +302,12 @@ class SyncService {
}
Future<void> _updateUserAssetsETag(List<User> 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);
}
Future<void> _clearUserAssetsETag(List<User> users) {
final ids = users.map((u) => u.id).toList();
final ids = users.map((u) => u.uid).toList();
return _eTagRepository.deleteByIds(ids);
}
@ -373,9 +379,10 @@ class SyncService {
);
// update shared users
final List<User> sharedUsers = album.sharedUsers.toList(growable: false);
final List<User> sharedUsers =
album.sharedUsers.map((u) => u.toDto()).toList(growable: false);
sharedUsers.sort((a, b) => a.id.compareTo(b.id));
final List<User> users = dto.remoteUsers.toList()
final List<User> users = dto.remoteUsers.map((u) => u.toDto()).toList()
..sort((a, b) => a.id.compareTo(b.id));
final List<String> userIdsToAdd = [];
final List<User> usersToUnlink = [];
@ -384,7 +391,7 @@ class SyncService {
sharedUsers,
compare: (User a, User b) => a.id.compareTo(b.id),
both: (a, b) => false,
onlyFirst: (User a) => userIdsToAdd.add(a.id),
onlyFirst: (User a) => userIdsToAdd.add(a.uid),
onlySecond: (User a) => usersToUnlink.add(a),
);
@ -392,7 +399,7 @@ class SyncService {
final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd);
await upsertAssetsWithExif(updated);
final assetsToLink = existingInDb + updated;
final usersToLink = await _userRepository.getByIds(userIdsToAdd);
final usersToLink = await _userRepository.getByUserIds(userIdsToAdd);
album.name = dto.name;
album.shared = dto.shared;
@ -416,7 +423,7 @@ class SyncService {
try {
await _assetRepository.transaction(() async {
await _assetRepository.updateAll(toUpdate);
await _albumRepository.addUsers(album, usersToLink);
await _albumRepository.addUsers(album, usersToLink.nonNulls.toList());
await _albumRepository.removeUsers(album, usersToUnlink);
await _albumRepository.addAssets(album, assetsToLink);
await _albumRepository.removeAssets(album, toUnlink);
@ -429,7 +436,7 @@ class SyncService {
}
if (album.shared || dto.shared) {
final userId = (await _userRepository.me()).isarId;
final userId = (_storeService.get(StoreKey.currentUser)).id;
final foreign =
await _assetRepository.getByAlbum(album, notOwnedBy: [userId]);
existing.addAll(foreign);
@ -482,8 +489,7 @@ class SyncService {
);
} 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
final userIds =
(await _userRepository.getAllAccessible()).map((user) => user.isarId);
final userIds = (await _userRepository.getAll()).map((user) => user.id);
final orphanedAssets =
await _assetRepository.getByAlbum(album, notOwnedBy: userIds);
deleteCandidates.addAll(orphanedAssets);
@ -566,7 +572,7 @@ class SyncService {
// general case, e.g. some assets have been deleted or there are excluded albums on iOS
final inDb = await _assetRepository.getByAlbum(
dbAlbum,
ownerId: (await _userRepository.me()).isarId,
ownerId: (_storeService.get(StoreKey.currentUser)).id,
sortBy: AssetSort.checksum,
);

View file

@ -1,41 +1,42 @@
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/asset.entity.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/infrastructure/store.provider.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/widgets/asset_grid/asset_grid_data_structure.dart';
final timelineServiceProvider = Provider<TimelineService>((ref) {
return TimelineService(
ref.watch(timelineRepositoryProvider),
ref.watch(userRepositoryProvider),
ref.watch(appSettingsServiceProvider),
ref.watch(storeServiceProvider),
);
});
class TimelineService {
final ITimelineRepository _timelineRepository;
final IUserRepository _userRepository;
final AppSettingsService _appSettingsService;
final StoreService _storeService;
const TimelineService(
this._timelineRepository,
this._userRepository,
this._appSettingsService,
this._storeService,
);
Future<List<int>> getTimelineUserIds() async {
final me = await _userRepository.me();
return _timelineRepository.getTimelineUserIds(me.isarId);
final me = _storeService.get(StoreKey.currentUser);
return _timelineRepository.getTimelineUserIds(me.id);
}
Stream<List<int>> watchTimelineUserIds() async* {
final me = await _userRepository.me();
yield* _timelineRepository.watchTimelineUsers(me.isarId);
final me = _storeService.get(StoreKey.currentUser);
yield* _timelineRepository.watchTimelineUsers(me.id);
}
Stream<RenderList> watchHomeTimeline(int userId) {
@ -50,15 +51,15 @@ class TimelineService {
}
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* {
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* {
@ -69,9 +70,9 @@ class TimelineService {
}
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() {
@ -96,9 +97,9 @@ class TimelineService {
}
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() {

View file

@ -1,12 +1,11 @@
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/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/infrastructure/store.provider.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:openapi/api.dart';
@ -14,16 +13,20 @@ final trashServiceProvider = Provider<TrashService>((ref) {
return TrashService(
ref.watch(apiServiceProvider),
ref.watch(assetRepositoryProvider),
ref.watch(userRepositoryProvider),
ref.watch(storeServiceProvider),
);
});
class TrashService {
final ApiService _apiService;
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 {
final remoteAssets = assetList.where((a) => a.isRemote);
@ -40,11 +43,11 @@ class TrashService {
}
Future<void> emptyTrash() async {
final user = await _userRepository.me();
final user = _storeService.get(StoreKey.currentUser);
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();
await _assetRepository.transaction(() async {
@ -71,11 +74,11 @@ class TrashService {
}
Future<void> restoreTrash() async {
final user = await _userRepository.me();
final user = _storeService.get(StoreKey.currentUser);
await _apiService.trashApi.restoreTrash();
final trashedAssets = await _assetRepository.getTrashAssets(user.isarId);
final trashedAssets = await _assetRepository.getTrashAssets(user.id);
final updatedAssets = trashedAssets.map((asset) {
asset.isTrashed = false;
return asset;

View file

@ -1,12 +1,12 @@
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/user.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/user.repository.dart';
import 'package:immich_mobile/repositories/user_api.repository.dart';
import 'package:immich_mobile/utils/diff.dart';
import 'package:logging/logging.dart';
@ -31,10 +31,6 @@ class UserService {
this._userRepository,
);
Future<List<User>> getUsers({bool self = false}) {
return _userRepository.getAll(self: self);
}
Future<({String profileImagePath})?> uploadProfileImage(XFile image) async {
try {
return await _userApiRepository.createProfileImage(
@ -47,6 +43,10 @@ class UserService {
}
}
Future<List<User>> getAll() async {
return await _userRepository.getAll();
}
Future<List<User>?> getUsersFromServer() async {
List<User>? users;
try {
@ -65,36 +65,44 @@ class UserService {
return null;
}
users.sortBy((u) => u.id);
sharedBy.sortBy((u) => u.id);
sharedWith.sortBy((u) => u.id);
users.sortBy((u) => u.uid);
sharedBy.sortBy((u) => u.uid);
sharedWith.sortBy((u) => u.uid);
final updatedSharedBy = <User>[];
diffSortedListsSync(
users,
sharedBy,
compare: (User a, User b) => a.id.compareTo(b.id),
both: (User a, User b) => a.isPartnerSharedBy = true,
onlyFirst: (_) {},
onlySecond: (_) {},
both: (User a, User b) {
updatedSharedBy.add(a.copyWith(isPartnerSharedBy: true));
return true;
},
onlyFirst: (User a) => updatedSharedBy.add(a),
onlySecond: (User b) => updatedSharedBy.add(b),
);
final updatedSharedWith = <User>[];
diffSortedListsSync(
users,
updatedSharedBy,
sharedWith,
compare: (User a, User b) => a.id.compareTo(b.id),
both: (User a, User b) {
a.isPartnerSharedWith = true;
a.inTimeline = b.inTimeline;
updatedSharedWith.add(
a.copyWith(inTimeline: b.inTimeline, isPartnerSharedWith: true),
);
return true;
},
onlyFirst: (_) {},
onlySecond: (_) {},
onlyFirst: (User a) => updatedSharedWith.add(a),
onlySecond: (User b) => updatedSharedWith.add(b),
);
return users;
return updatedSharedWith;
}
Future<void> clearTable() {
return _userRepository.clearTable();
return _userRepository.deleteAll();
}
}

View file

@ -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/etag.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/log.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/store.repository.dart';
import 'package:isar/isar.dart';

View file

@ -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/etag.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/user.entity.dart';
import 'package:isar/isar.dart';
const int targetVersion = 8;

View file

@ -58,7 +58,7 @@ class AlbumThumbnailCard extends StatelessWidget {
// Add the owner name to the subtitle
String? owner;
if (showOwner) {
if (album.ownerId == Store.get(StoreKey.currentUser).id) {
if (album.ownerId == Store.get(StoreKey.currentUser).uid) {
owner = 'album_thumbnail_owned'.tr();
} else if (album.ownerName != null) {
owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]);

View file

@ -5,25 +5,25 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.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/pages/editing/edit.page.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/asset.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/download.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/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/pages/editing/edit.page.dart';
class BottomGalleryBar extends ConsumerWidget {
final ValueNotifier<int> assetIndex;
@ -49,7 +49,7 @@ class BottomGalleryBar extends ConsumerWidget {
if (asset == null) {
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 stackId = asset.stackId;

View file

@ -81,7 +81,7 @@ class DescriptionInput extends HookConsumerWidget {
}
return TextField(
enabled: owner?.isarId == asset.ownerId,
enabled: owner?.id == asset.ownerId,
focusNode: focusNode,
onTap: () => isFocus.value = true,
onChanged: (value) {

View file

@ -67,8 +67,9 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
profileImagePath,
);
if (user != null) {
user.profileImagePath = profileImagePath;
await Store.put(StoreKey.currentUser, user);
final updatedUser =
user.copyWith(profileImagePath: profileImagePath);
await Store.put(StoreKey.currentUser, updatedUser);
ref.read(currentUserProvider.notifier).refresh();
}
}

View file

@ -1,8 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.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/user.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/services/api.service.dart';

View file

@ -4,8 +4,8 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/user.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/widgets/common/transparent_image.dart';
@ -33,7 +33,7 @@ class UserCircleAvatar extends ConsumerWidget {
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary
color: isDarkTheme && user.avatarColor == AvatarColor.primary
? Colors.black
: Colors.white,
),
@ -42,7 +42,7 @@ class UserCircleAvatar extends ConsumerWidget {
return CircleAvatar(
backgroundColor: user.avatarColor.toColor(),
radius: radius,
child: user.profileImagePath.isEmpty
child: user.profileImagePath == null
? textIcon
: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(50)),

View file

@ -1,4 +1,5 @@
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'asset.stub.dart';
import 'user.stub.dart';
@ -26,7 +27,7 @@ final class AlbumStub {
shared: true,
activityEnabled: false,
endDate: DateTime(2020),
)..sharedUsers.addAll([UserStub.admin]);
)..sharedUsers.addAll([User.fromDto(UserStub.admin)]);
static final oneAsset = Album(
name: "album-with-single-asset",
@ -53,7 +54,7 @@ final class AlbumStub {
)
..assets.addAll([AssetStub.image1, AssetStub.image2])
..activityEnabled = true
..owner.value = UserStub.admin;
..owner.value = User.fromDto(UserStub.admin);
static final create2020end2020Album = Album(
name: "create2020update2020Album",

View file

@ -1,35 +1,35 @@
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
final class UserStub {
abstract final class UserStub {
const UserStub._();
static final admin = User(
id: "admin",
updatedAt: DateTime(2021),
uid: "admin",
email: "admin@test.com",
name: "admin",
avatarColor: AvatarColorEnum.green,
profileImagePath: '',
isAdmin: true,
updatedAt: DateTime(2021),
profileImagePath: null,
avatarColor: AvatarColor.primary,
);
static final user1 = User(
id: "user1",
updatedAt: DateTime(2022),
uid: "user1",
email: "user1@test.com",
name: "user1",
avatarColor: AvatarColorEnum.red,
profileImagePath: '',
isAdmin: false,
updatedAt: DateTime(2022),
profileImagePath: null,
avatarColor: AvatarColor.primary,
);
static final user2 = User(
id: "user2",
updatedAt: DateTime(2023),
uid: "user2",
email: "user2@test.com",
name: "user2",
avatarColor: AvatarColorEnum.primary,
profileImagePath: '',
isAdmin: false,
updatedAt: DateTime(2023),
profileImagePath: null,
avatarColor: AvatarColor.primary,
);
}

View file

@ -3,7 +3,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.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/repositories/store.repository.dart';
import 'package:isar/isar.dart';

View file

@ -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/asset.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/models/activities/activity.model.dart';
import 'package:immich_mobile/pages/common/activities.page.dart';
@ -96,7 +96,7 @@ void main() {
await db.writeTxn(() async {
await db.clear();
// 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.albums.put(AlbumStub.twoAsset);
await AlbumStub.twoAsset.owner.save();

View file

@ -1,5 +1,5 @@
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:mocktail/mocktail.dart';

View file

@ -1,16 +1,16 @@
import 'package:flutter/widgets.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/user.model.dart';
import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/etag.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/store.repository.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:mocktail/mocktail.dart';
@ -56,8 +56,9 @@ void main() {
final MockAlbumMediaRepository albumMediaRepository =
MockAlbumMediaRepository();
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
final owner = User(
id: "1",
uid: "1",
updatedAt: DateTime.now(),
email: "a@b.c",
name: "first last",
@ -93,20 +94,20 @@ void main() {
assetRepository,
exifInfoRepository,
userRepository,
StoreService.I,
eTagRepository,
);
when(() => eTagRepository.get(owner.isarId))
.thenAnswer((_) async => ETag(id: owner.id, time: DateTime.now()));
when(() => eTagRepository.get(owner.id))
.thenAnswer((_) async => ETag(id: owner.uid, time: DateTime.now()));
when(() => eTagRepository.deleteByIds(["1"])).thenAnswer((_) async {});
when(() => eTagRepository.upsertAll(any())).thenAnswer((_) async {});
when(() => userRepository.me()).thenAnswer((_) async => owner);
when(() => userRepository.getAll(sortBy: UserSort.id))
.thenAnswer((_) async => [owner]);
when(() => userRepository.getAllAccessible())
// when(() => userRepository.me()).thenAnswer((_) async => owner);
when(() => userRepository.getAll(sortBy: SortUserBy.id))
.thenAnswer((_) async => [owner]);
when(() => userRepository.getAll()).thenAnswer((_) async => [owner]);
when(
() => assetRepository.getAll(
ownerId: owner.isarId,
ownerId: owner.id,
sortBy: AssetSort.checksum,
),
).thenAnswer((_) async => initialAssets);
@ -180,7 +181,7 @@ void main() {
expect(c1, isTrue);
when(
() => assetRepository.getAll(
ownerId: owner.isarId,
ownerId: owner.id,
sortBy: AssetSort.checksum,
),
).thenAnswer((_) async => remoteAssets);
@ -194,7 +195,7 @@ void main() {
final currentState = [...remoteAssets];
when(
() => assetRepository.getAll(
ownerId: owner.isarId,
ownerId: owner.id,
sortBy: AssetSort.checksum,
),
).thenAnswer((_) async => currentState);

View file

@ -1,4 +1,5 @@
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_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
@ -9,7 +10,6 @@ import 'package:immich_mobile/interfaces/auth_api.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/interfaces/user.interface.dart';
import 'package:mocktail/mocktail.dart';
class MockAlbumRepository extends Mock implements IAlbumRepository {}

View file

@ -146,7 +146,7 @@ void main() {
() => albumApiRepository.create(
"name",
assetIds: [AssetStub.image1.remoteId!],
sharedUserIds: [UserStub.user1.id],
sharedUserIds: [UserStub.user1.uid],
),
).called(1);
verify(
@ -204,7 +204,7 @@ void main() {
when(
() => albumRepository.addUsers(
AlbumStub.emptyAlbum,
AlbumStub.emptyAlbum.sharedUsers.toList(),
AlbumStub.emptyAlbum.sharedUsers.map((u) => u.toDto()).toList(),
),
).thenAnswer((_) async => AlbumStub.emptyAlbum);
@ -214,7 +214,7 @@ void main() {
final result = await sut.addUsers(
AlbumStub.emptyAlbum,
[UserStub.user2.id],
[UserStub.user2.uid],
);
expect(result, true);

View file

@ -1,7 +1,9 @@
import 'package:flutter_test/flutter_test.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:mocktail/mocktail.dart';
import '../fixtures/asset.stub.dart';
import '../fixtures/user.stub.dart';
import '../repository.mocks.dart';
@ -33,25 +35,32 @@ void main() {
)
..remoteThumbnailAssetId = AssetStub.image1.remoteId
..assets.addAll([AssetStub.image1, AssetStub.image1])
..owner.value = UserStub.user1
..sharedUsers.addAll([UserStub.admin, UserStub.admin]);
..owner.value = User.fromDto(UserStub.user1)
..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);
when(() => assetRepository.getByRemoteId(AssetStub.image1.remoteId!))
.thenAnswer((_) async => AssetStub.image1);
when(() => userRepository.getByIds(any()))
when(() => userRepository.getByUserIds(any()))
.thenAnswer((_) async => [UserStub.user1, UserStub.user2]);
when(() => assetRepository.getAllByRemoteId(any()))
.thenAnswer((_) async => [AssetStub.image1, AssetStub.image2]);
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.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});
});

View file

@ -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/etag.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/log.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:mocktail/mocktail.dart';