more refactors

This commit is contained in:
shenlong-tanwen
2024-10-19 14:34:42 +05:30
parent e8bb9a3934
commit 3b8951fde6
46 changed files with 598 additions and 435 deletions
@@ -0,0 +1,10 @@
abstract interface class IAuthenticationApiRepository {
/// Returns the access token on successful login
Future<String?> login(String email, String password);
/// Returns the OAuth URL
Future<String?> startOAuth({required String redirectUri});
/// Returns the access token on successful oauth login
Future<String?> finishOAuth(String url);
}
@@ -0,0 +1,13 @@
import 'package:immich_mobile/domain/models/server-info/server_config.model.dart';
import 'package:immich_mobile/domain/models/server-info/server_features.model.dart';
abstract interface class IServerApiRepository {
/// Pings and check if server is reachable
Future<void> pingServer();
/// Fetches the list of enabled features in the server
Future<ServerFeatures?> getServerFeatures();
/// Fetches the server configuration and settings
Future<ServerConfig?> getServerConfig();
}
@@ -0,0 +1,11 @@
import 'package:immich_mobile/domain/models/asset.model.dart';
abstract interface class ISyncApiRepository {
/// Fetches the full assets for the user in batches
Future<List<Asset>?> getFullSyncForUser({
String? lastId,
required int limit,
required DateTime updatedUntil,
String? userId,
});
}
@@ -0,0 +1,6 @@
import 'package:immich_mobile/domain/models/user.model.dart';
abstract interface class IUserApiRepository {
/// Fetches the current users meta data
Future<User?> getMyUser();
}
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/presentation/modules/theme/models/app_theme.model.dart';
import 'package:immich_mobile/presentation/theme/app_theme.dart';
// AppSetting needs to store UI specific settings as well as domain specific settings
// This model is the only exclusion which refers to entities from the presentation layer
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/utils/collection_util.dart';
import 'package:immich_mobile/utils/extensions/string.extension.dart';
import 'package:openapi/api.dart';
enum AssetType {
// do not change this order!
@@ -49,19 +47,6 @@ class Asset {
this.livePhotoVideoId,
});
factory Asset.remote(AssetResponseDto dto) => Asset(
remoteId: dto.id,
createdTime: dto.fileCreatedAt,
duration: dto.duration.tryParseInt() ?? 0,
height: dto.exifInfo?.exifImageHeight?.toInt(),
width: dto.exifInfo?.exifImageWidth?.toInt(),
hash: dto.checksum,
name: dto.originalFileName,
livePhotoVideoId: dto.livePhotoVideoId,
modifiedTime: dto.fileModifiedAt,
type: _toAssetType(dto.type),
);
Asset copyWith({
int? id,
String? name,
@@ -177,10 +162,3 @@ class Asset {
static int compareByLocalId(Asset a, Asset b) =>
CollectionUtil.compareToNullable(a.localId, b.localId);
}
AssetType _toAssetType(AssetTypeEnum type) => switch (type) {
AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.IMAGE => AssetType.image,
AssetTypeEnum.VIDEO => AssetType.video,
_ => AssetType.other,
};
@@ -1,5 +1,3 @@
import 'package:openapi/api.dart';
class ServerConfig {
final String? oauthButtonText;
@@ -11,12 +9,7 @@ class ServerConfig {
);
}
factory ServerConfig.fromDto(ServerConfigDto dto) => ServerConfig(
oauthButtonText:
dto.oauthButtonText.isEmpty ? null : dto.oauthButtonText,
);
const ServerConfig.reset() : oauthButtonText = null;
const ServerConfig.initial() : oauthButtonText = null;
@override
String toString() =>
@@ -17,9 +17,9 @@ class ServerFeatureConfig {
);
}
const ServerFeatureConfig.reset()
: features = const ServerFeatures.reset(),
config = const ServerConfig.reset();
const ServerFeatureConfig.initial()
: features = const ServerFeatures.initial(),
config = const ServerConfig.initial();
@override
String toString() =>
@@ -1,5 +1,3 @@
import 'package:openapi/api.dart';
class ServerFeatures {
final bool hasPasswordLogin;
final bool hasOAuthLogin;
@@ -16,12 +14,7 @@ class ServerFeatures {
);
}
factory ServerFeatures.fromDto(ServerFeaturesDto dto) => ServerFeatures(
hasPasswordLogin: dto.passwordLogin,
hasOAuthLogin: dto.oauth,
);
const ServerFeatures.reset()
const ServerFeatures.initial()
: hasPasswordLogin = true,
hasOAuthLogin = false;
+1 -1
View File
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/utils/store_converters.dart';
import 'package:immich_mobile/presentation/modules/theme/models/app_theme.model.dart';
import 'package:immich_mobile/presentation/theme/app_theme.dart';
@immutable
class StoreValue<T> {
@@ -1,7 +1,5 @@
import 'dart:ui';
import 'package:openapi/api.dart' as api;
class User {
const User({
required this.id,
@@ -96,25 +94,6 @@ class User {
memoryEnabled.hashCode ^
avatarColor.hashCode;
}
factory User.fromAdminDto(
api.UserAdminResponseDto userDto, [
api.UserPreferencesResponseDto? userPreferences,
]) {
return User(
id: userDto.id,
updatedAt: DateTime.now(),
name: userDto.name,
email: userDto.email,
isAdmin: userDto.isAdmin,
quotaSizeInBytes: userDto.quotaSizeInBytes ?? 0,
quotaUsageInBytes: userDto.quotaUsageInBytes ?? 0,
inTimeline: true,
profileImagePath: userDto.profileImagePath,
memoryEnabled: userPreferences?.memories.enabled ?? true,
avatarColor: userDto.avatarColor.toEnum(),
);
}
}
enum UserAvatarColor {
@@ -131,34 +110,6 @@ enum UserAvatarColor {
amber,
}
extension AvatarColorEnumHelper on api.UserAvatarColor {
UserAvatarColor toEnum() {
switch (this) {
case api.UserAvatarColor.primary:
return UserAvatarColor.primary;
case api.UserAvatarColor.pink:
return UserAvatarColor.pink;
case api.UserAvatarColor.red:
return UserAvatarColor.red;
case api.UserAvatarColor.yellow:
return UserAvatarColor.yellow;
case api.UserAvatarColor.blue:
return UserAvatarColor.blue;
case api.UserAvatarColor.green:
return UserAvatarColor.green;
case api.UserAvatarColor.purple:
return UserAvatarColor.purple;
case api.UserAvatarColor.orange:
return UserAvatarColor.orange;
case api.UserAvatarColor.gray:
return UserAvatarColor.gray;
case api.UserAvatarColor.amber:
return UserAvatarColor.amber;
}
return UserAvatarColor.primary;
}
}
extension AvatarColorToColorHelper on UserAvatarColor {
Color toColor([bool isDarkTheme = false]) {
switch (this) {
@@ -10,16 +10,16 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class AlbumRepository with LogMixin implements IAlbumRepository {
final DriftDatabaseRepository _db;
const AlbumRepository(this._db);
const AlbumRepository({required DriftDatabaseRepository db}) : _db = db;
@override
FutureOr<Album?> upsert(Album album) async {
try {
final albumData = _toEntity(album);
final data = await _db.into(_db.album).insertReturningOrNull(
albumData,
onConflict: DoUpdate((_) => albumData, target: [_db.album.localId]),
);
final data = await _db.album.insertReturningOrNull(
albumData,
onConflict: DoUpdate((_) => albumData, target: [_db.album.localId]),
);
if (data != null) {
return _toModel(data);
}
@@ -11,16 +11,16 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class AlbumToAssetRepository with LogMixin implements IAlbumToAssetRepository {
final DriftDatabaseRepository _db;
const AlbumToAssetRepository(this._db);
const AlbumToAssetRepository({required DriftDatabaseRepository db})
: _db = db;
@override
FutureOr<bool> addAssetIds(int albumId, Iterable<int> assetIds) async {
try {
await _db.albumToAsset.insertAll(
assetIds.map((a) => AlbumToAssetCompanion.insert(
assetId: a,
albumId: albumId,
)),
assetIds.map(
(a) => AlbumToAssetCompanion.insert(assetId: a, albumId: albumId),
),
onConflict: DoNothing(
target: [_db.albumToAsset.assetId, _db.albumToAsset.albumId],
),
@@ -10,17 +10,16 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class AlbumETagRepository with LogMixin implements IAlbumETagRepository {
final DriftDatabaseRepository _db;
const AlbumETagRepository(this._db);
const AlbumETagRepository({required DriftDatabaseRepository db}) : _db = db;
@override
FutureOr<bool> upsert(AlbumETag albumETag) async {
try {
final entity = _toEntity(albumETag);
await _db.into(_db.albumETag).insert(
entity,
onConflict:
DoUpdate((_) => entity, target: [_db.albumETag.albumId]),
);
await _db.albumETag.insertOne(
entity,
onConflict: DoUpdate((_) => entity, target: [_db.albumETag.albumId]),
);
return true;
} catch (e, s) {
log.e("Error while adding an album etag to the DB", e, s);
@@ -30,10 +29,9 @@ class AlbumETagRepository with LogMixin implements IAlbumETagRepository {
@override
FutureOr<AlbumETag?> get(int albumId) async {
return await _db.managers.albumETag
.filter((r) => r.albumId.id.equals(albumId))
.map(_toModel)
.getSingleOrNull();
final query = _db.albumETag.select()
..where((r) => r.albumId.equals(albumId));
return await query.map(_toModel).getSingleOrNull();
}
}
@@ -0,0 +1,51 @@
import 'package:immich_mobile/domain/interfaces/api/authentication_api.interface.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart';
class AuthenticationApiRepository
with LogMixin
implements IAuthenticationApiRepository {
final AuthenticationApi _authenticationApi;
final OAuthApi _oAuthApi;
const AuthenticationApiRepository({
required AuthenticationApi authenticationApi,
required OAuthApi oAuthApi,
}) : _authenticationApi = authenticationApi,
_oAuthApi = oAuthApi;
@override
Future<String?> login(String email, String password) async {
try {
final response = await _authenticationApi
.login(LoginCredentialDto(email: email, password: password));
return response?.accessToken;
} catch (e, s) {
log.e("Exception occured while performing password login", e, s);
}
return null;
}
@override
Future<String?> startOAuth({required String redirectUri}) async {
try {
final response =
await _oAuthApi.startOAuth(OAuthConfigDto(redirectUri: redirectUri));
return response?.url;
} catch (e, s) {
log.e("Exception occured while starting oauth login", e, s);
}
return null;
}
@override
Future<String?> finishOAuth(String url) async {
try {
final response = await _oAuthApi.finishOAuth(OAuthCallbackDto(url: url));
return response?.accessToken;
} catch (e, s) {
log.e("Exception occured while finishing oauth login", e, s);
}
return null;
}
}
@@ -0,0 +1,52 @@
import 'package:immich_mobile/domain/interfaces/api/server_api.interface.dart';
import 'package:immich_mobile/domain/models/server-info/server_config.model.dart';
import 'package:immich_mobile/domain/models/server-info/server_features.model.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart' as api;
class ServerApiRepository with LogMixin implements IServerApiRepository {
final api.ServerApi _serverApi;
const ServerApiRepository({required api.ServerApi serverApi})
: _serverApi = serverApi;
@override
Future<void> pingServer() async {
await _serverApi.pingServer();
}
@override
Future<ServerConfig?> getServerConfig() async {
try {
final config = await _serverApi.getServerConfig();
if (config != null) {
return _fromConfigDto(config);
}
} catch (e, s) {
log.e("Exception occured while fetching server config", e, s);
}
return null;
}
@override
Future<ServerFeatures?> getServerFeatures() async {
try {
final features = await _serverApi.getServerFeatures();
if (features != null) {
return _fromFeatureDto(features);
}
} catch (e, s) {
log.e("Exception occured while fetching server features", e, s);
}
return null;
}
}
ServerConfig _fromConfigDto(api.ServerConfigDto dto) => ServerConfig(
oauthButtonText: dto.oauthButtonText.isEmpty ? null : dto.oauthButtonText,
);
ServerFeatures _fromFeatureDto(api.ServerFeaturesDto dto) => ServerFeatures(
hasPasswordLogin: dto.passwordLogin,
hasOAuthLogin: dto.oauth,
);
@@ -0,0 +1,52 @@
import 'package:immich_mobile/domain/interfaces/api/sync_api.interface.dart';
import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/utils/extensions/string.extension.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart';
class SyncApiRepository with LogMixin implements ISyncApiRepository {
final SyncApi _syncApi;
const SyncApiRepository({required SyncApi syncApi}) : _syncApi = syncApi;
@override
Future<List<Asset>?> getFullSyncForUser({
String? lastId,
required int limit,
required DateTime updatedUntil,
String? userId,
}) async {
try {
final res = await _syncApi.getFullSyncForUser(AssetFullSyncDto(
lastId: lastId,
limit: limit,
updatedUntil: updatedUntil,
userId: userId,
));
return res?.map(_fromAssetResponseDto).toList();
} catch (e) {
log.e("Error fetching full asset sync for user", e);
return null;
}
}
}
Asset _fromAssetResponseDto(AssetResponseDto dto) => Asset(
remoteId: dto.id,
createdTime: dto.fileCreatedAt,
duration: dto.duration.tryParseInt() ?? 0,
height: dto.exifInfo?.exifImageHeight?.toInt(),
width: dto.exifInfo?.exifImageWidth?.toInt(),
hash: dto.checksum,
name: dto.originalFileName,
livePhotoVideoId: dto.livePhotoVideoId,
modifiedTime: dto.fileModifiedAt,
type: _toAssetType(dto.type),
);
AssetType _toAssetType(AssetTypeEnum type) => switch (type) {
AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.IMAGE => AssetType.image,
AssetTypeEnum.VIDEO => AssetType.video,
_ => AssetType.other,
};
@@ -0,0 +1,80 @@
import 'package:immich_mobile/domain/interfaces/api/user_api.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart' as model;
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart';
class UserApiRepository with LogMixin implements IUserApiRepository {
final UsersApi _usersApi;
const UserApiRepository({required UsersApi usersApi}) : _usersApi = usersApi;
@override
Future<model.User?> getMyUser() async {
try {
final [
userDto as UserAdminResponseDto?,
preferencesDto as UserPreferencesResponseDto?
] = await Future.wait([
_usersApi.getMyUser(),
_usersApi.getMyPreferences(),
]);
if (userDto == null) {
log.e("Cannot fetch my user.");
return null;
}
return _fromAdminDto(userDto, preferencesDto);
} catch (e, s) {
log.e("Error while fetching my user", e, s);
}
return null;
}
}
model.User _fromAdminDto(
UserAdminResponseDto userDto, [
UserPreferencesResponseDto? userPreferences,
]) {
return model.User(
id: userDto.id,
updatedAt: DateTime.now(),
name: userDto.name,
email: userDto.email,
isAdmin: userDto.isAdmin,
quotaSizeInBytes: userDto.quotaSizeInBytes ?? 0,
quotaUsageInBytes: userDto.quotaUsageInBytes ?? 0,
inTimeline: true,
profileImagePath: userDto.profileImagePath,
memoryEnabled: userPreferences?.memories.enabled ?? true,
avatarColor: userDto.avatarColor.toEnum(),
);
}
extension _AvatarColorEnumHelper on UserAvatarColor {
model.UserAvatarColor toEnum() {
switch (this) {
case UserAvatarColor.primary:
return model.UserAvatarColor.primary;
case UserAvatarColor.pink:
return model.UserAvatarColor.pink;
case UserAvatarColor.red:
return model.UserAvatarColor.red;
case UserAvatarColor.yellow:
return model.UserAvatarColor.yellow;
case UserAvatarColor.blue:
return model.UserAvatarColor.blue;
case UserAvatarColor.green:
return model.UserAvatarColor.green;
case UserAvatarColor.purple:
return model.UserAvatarColor.purple;
case UserAvatarColor.orange:
return model.UserAvatarColor.orange;
case UserAvatarColor.gray:
return model.UserAvatarColor.gray;
case UserAvatarColor.amber:
return model.UserAvatarColor.amber;
}
return model.UserAvatarColor.primary;
}
}
@@ -11,7 +11,7 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class AssetRepository with LogMixin implements IAssetRepository {
final DriftDatabaseRepository _db;
const AssetRepository(this._db);
const AssetRepository({required DriftDatabaseRepository db}) : _db = db;
@override
Future<bool> upsertAll(Iterable<Asset> assets) async {
@@ -12,7 +12,8 @@ class DeviceAssetToHashRepository
implements IDeviceAssetToHashRepository {
final DriftDatabaseRepository _db;
const DeviceAssetToHashRepository(this._db);
const DeviceAssetToHashRepository({required DriftDatabaseRepository db})
: _db = db;
@override
FutureOr<bool> upsertAll(Iterable<DeviceAssetToHash> assetHash) async {
@@ -31,10 +32,9 @@ class DeviceAssetToHashRepository
@override
Future<List<DeviceAssetToHash>> getForIds(Iterable<String> localIds) async {
return await _db.managers.deviceAssetToHash
.filter((f) => f.localId.isIn(localIds))
.map(_toModel)
.get();
final query = _db.deviceAssetToHash.select()
..where((f) => f.localId.isIn(localIds));
return await query.map(_toModel).get();
}
@override
@@ -10,7 +10,7 @@ import 'package:immich_mobile/domain/repositories/database.repository.dart';
class LogRepository implements ILogRepository {
final DriftDatabaseRepository _db;
const LogRepository(this._db);
const LogRepository({required DriftDatabaseRepository db}) : _db = db;
@override
Future<List<LogMessage>> getAll() async {
@@ -32,7 +32,7 @@ class LogRepository implements ILogRepository {
@override
FutureOr<bool> create(LogMessage log) async {
try {
await _db.into(_db.logs).insert(_toEntity(log));
await _db.logs.insertOne(_toEntity(log));
return true;
} catch (e) {
debugPrint("Error while adding a log to the DB - $e");
@@ -56,7 +56,7 @@ class LogRepository implements ILogRepository {
@override
FutureOr<bool> deleteAll() async {
try {
await _db.managers.logs.delete();
await _db.logs.deleteAll();
return true;
} catch (e) {
debugPrint("Error while clearning the logs in DB - $e");
@@ -9,7 +9,7 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class RenderListRepository with LogMixin implements IRenderListRepository {
final DriftDatabaseRepository _db;
const RenderListRepository(this._db);
const RenderListRepository({required DriftDatabaseRepository db}) : _db = db;
@override
Stream<RenderList> watchAll() {
@@ -10,7 +10,7 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class StoreRepository with LogMixin implements IStoreRepository {
final DriftDatabaseRepository _db;
const StoreRepository(this._db);
const StoreRepository({required DriftDatabaseRepository db}) : _db = db;
@override
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key) async {
@@ -10,7 +10,7 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class UserRepository with LogMixin implements IUserRepository {
final DriftDatabaseRepository _db;
const UserRepository(this._db);
const UserRepository({required DriftDatabaseRepository db}) : _db = db;
@override
FutureOr<User?> getForId(String userId) async {
@@ -4,7 +4,7 @@ import 'package:immich_mobile/domain/models/app_setting.model.dart';
class AppSettingService {
final IStoreRepository _store;
const AppSettingService(this._store);
const AppSettingService({required IStoreRepository store}) : _store = store;
Future<T> get<T>(AppSetting<T> setting) async {
final value = await _store.tryGet(setting.storeKey);
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:immich_mobile/domain/interfaces/api/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
import 'package:immich_mobile/domain/models/asset.model.dart';
@@ -8,10 +9,8 @@ import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/collection_util.dart';
import 'package:immich_mobile/utils/constants/globals.dart';
import 'package:immich_mobile/utils/immich_api_client.dart';
import 'package:immich_mobile/utils/isolate_helper.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart';
class AssetSyncService with LogMixin {
const AssetSyncService();
@@ -36,9 +35,9 @@ class AssetSyncService with LogMixin {
int? limit,
}) async {
try {
final syncClient = di<ImApiClient>().getSyncApi();
final db = di<IDatabaseRepository>();
final assetRepo = di<IAssetRepository>();
final syncApiRepo = di<ISyncApiRepository>();
final chunkSize = limit ?? kFullSyncChunkSize;
final updatedTill = updatedUtil ?? DateTime.now().toUtc();
@@ -50,18 +49,16 @@ class AssetSyncService with LogMixin {
"Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}",
);
final assets = await syncClient.getFullSyncForUser(AssetFullSyncDto(
final assetsFromServer = await syncApiRepo.getFullSyncForUser(
limit: chunkSize,
updatedUntil: updatedTill,
lastId: lastAssetId,
userId: user.id,
));
if (assets == null) {
);
if (assetsFromServer == null) {
break;
}
final assetsFromServer = assets.map(Asset.remote).toList();
await db.txn(() async {
final assetsInDb =
await assetRepo.getForHashes(assetsFromServer.map((a) => a.hash));
@@ -73,8 +70,8 @@ class AssetSyncService with LogMixin {
);
});
lastAssetId = assets.lastOrNull?.id;
if (assets.length != chunkSize) break;
lastAssetId = assetsFromServer.lastOrNull?.remoteId;
if (assetsFromServer.length != chunkSize) break;
}
return true;
@@ -4,28 +4,31 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:http/http.dart';
import 'package:immich_mobile/domain/interfaces/api/authentication_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/api/server_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/api/user_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/immich_api_client.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart';
// Cannot add dependency repos to constructor as this requires the newly registered API client from login
// and not a cached repos from DI
class LoginService with LogMixin {
const LoginService();
Future<bool> isEndpointAvailable(Uri uri, {ImApiClient? client}) async {
Future<bool> isEndpointAvailable(Uri uri) async {
String baseUrl = uri.toString();
if (!baseUrl.endsWith('/api')) {
baseUrl += '/api';
}
final serverAPI =
client?.getServerApi() ?? ImApiClient(endpoint: baseUrl).getServerApi();
await ServiceLocator.registerApiClient(baseUrl);
try {
await serverAPI.pingServer();
await di<IServerApiRepository>().pingServer();
} catch (e) {
log.e("Exception occured while validating endpoint", e);
return false;
@@ -61,12 +64,7 @@ class LoginService with LogMixin {
Future<String?> passwordLogin(String email, String password) async {
try {
final loginResponse =
await di<ImApiClient>().getAuthenticationApi().login(
LoginCredentialDto(email: email, password: password),
);
return loginResponse?.accessToken;
return await di<IAuthenticationApiRepository>().login(email, password);
} catch (e, s) {
log.e("Exception occured while performing password login", e, s);
}
@@ -75,16 +73,14 @@ class LoginService with LogMixin {
Future<String?> oAuthLogin() async {
const String oAuthCallbackSchema = 'app.immich';
final oAuthApi = di<ImApiClient>().getOAuthApi();
final authApi = di<IAuthenticationApiRepository>();
try {
final oAuthUrl = await oAuthApi.startOAuth(
OAuthConfigDto(redirectUri: "$oAuthCallbackSchema:/"),
final oAuthUrl = await authApi.startOAuth(
redirectUri: "$oAuthCallbackSchema:/",
);
final oAuthUrlRes = oAuthUrl?.url;
if (oAuthUrlRes == null) {
if (oAuthUrl == null) {
log.e(
"oAuth Server URL not available. Kindly ensure oAuth login is enabled in the server",
);
@@ -92,15 +88,11 @@ class LoginService with LogMixin {
}
final oAuthCallbackUrl = await FlutterWebAuth2.authenticate(
url: oAuthUrlRes,
url: oAuthUrl,
callbackUrlScheme: oAuthCallbackSchema,
);
final loginResponse = await oAuthApi.finishOAuth(
OAuthCallbackDto(url: oAuthCallbackUrl),
);
return loginResponse?.accessToken;
return await authApi.finishOAuth(oAuthCallbackUrl);
} catch (e) {
log.e("Exception occured while performing oauth login", e);
}
@@ -114,8 +106,7 @@ class LoginService with LogMixin {
return false;
}
ServiceLocator.registerApiClient(serverEndpoint);
ServiceLocator.registerPostValidationServices();
await ServiceLocator.registerApiClient(serverEndpoint);
ServiceLocator.registerPostGlobalStates();
final accessToken =
@@ -124,10 +115,10 @@ class LoginService with LogMixin {
return false;
}
/// Set token to interceptor
// Set token to interceptor
await di<ImApiClient>().init(accessToken: accessToken);
final user = await di<UserService>().getMyUser().timeout(
final user = await di<IUserApiRepository>().getMyUser().timeout(
const Duration(seconds: 10),
// ignore: function-always-returns-null
onTimeout: () {
@@ -1,34 +0,0 @@
import 'package:immich_mobile/domain/models/server-info/server_config.model.dart';
import 'package:immich_mobile/domain/models/server-info/server_features.model.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart';
class ServerInfoService with LogMixin {
final ServerApi _serverInfo;
const ServerInfoService(this._serverInfo);
Future<ServerFeatures?> getServerFeatures() async {
try {
final dto = await _serverInfo.getServerFeatures();
if (dto != null) {
return ServerFeatures.fromDto(dto);
}
} catch (e, s) {
log.e("Error while fetching server features", e, s);
}
return null;
}
Future<ServerConfig?> getServerConfig() async {
try {
final dto = await _serverInfo.getServerConfig();
if (dto != null) {
return ServerConfig.fromDto(dto);
}
} catch (e, s) {
log.e("Error while fetching server config", e, s);
}
return null;
}
}
@@ -1,31 +0,0 @@
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart';
class UserService with LogMixin {
final UsersApi _userApi;
const UserService(this._userApi);
Future<User?> getMyUser() async {
try {
final [
userDto as UserAdminResponseDto?,
preferencesDto as UserPreferencesResponseDto?
] = await Future.wait([
_userApi.getMyUser(),
_userApi.getMyPreferences(),
]);
if (userDto == null) {
log.e("Cannot fetch my user.");
return null;
}
return User.fromAdminDto(userDto, preferencesDto);
} catch (e, s) {
log.e("Error while fetching my user", e, s);
}
return null;
}
}
@@ -0,0 +1,28 @@
import 'dart:async';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/renderlist.interface.dart';
import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/domain/models/render_list.model.dart';
import 'package:immich_mobile/service_locator.dart';
typedef RenderListStreamProvider = Stream<RenderList> Function();
typedef RenderListAssetProvider = FutureOr<List<Asset>> Function({
int? offset,
int? limit,
});
class RenderListProvider {
final RenderListStreamProvider renderStreamProvider;
final RenderListAssetProvider renderAssetProvider;
const RenderListProvider({
required this.renderStreamProvider,
required this.renderAssetProvider,
});
factory RenderListProvider.mainTimeline() => RenderListProvider(
renderStreamProvider: () => di<IRenderListRepository>().watchAll(),
renderAssetProvider: di<IAssetRepository>().getAll,
);
}
+42 -7
View File
@@ -1,11 +1,10 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:immich_mobile/i18n/strings.g.dart';
import 'package:immich_mobile/presentation/modules/theme/models/app_theme.model.dart';
import 'package:immich_mobile/presentation/modules/theme/states/app_theme.state.dart';
import 'package:immich_mobile/presentation/modules/theme/widgets/app_theme_builder.widget.dart';
import 'package:immich_mobile/presentation/router/router.dart';
import 'package:immich_mobile/presentation/states/app_theme.state.dart';
import 'package:immich_mobile/presentation/theme/app_theme.dart';
import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/constants/globals.dart';
@@ -22,9 +21,9 @@ class _ImAppState extends State<ImApp> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
return TranslationProvider(
child: BlocBuilder<AppThemeCubit, AppTheme>(
bloc: di(),
builder: (_, appTheme) => AppThemeBuilder(
child: ValueListenableBuilder(
valueListenable: di<AppThemeProvider>(),
builder: (_, appTheme, __) => _AppThemeBuilder(
theme: appTheme,
builder: (ctx, lightTheme, darkTheme) => MaterialApp.router(
debugShowCheckedModeBanner: false,
@@ -41,3 +40,39 @@ class _ImAppState extends State<ImApp> with WidgetsBindingObserver {
);
}
}
class _AppThemeBuilder extends StatelessWidget {
const _AppThemeBuilder({required this.theme, required this.builder});
/// Current app theme to switch the theme data used
final AppTheme theme;
/// Builds the child widget of this widget, providing a light and dark [ThemeData] based on the
/// [theme] passed.
final Widget Function(
BuildContext context,
ThemeData lightTheme,
ThemeData darkTheme,
) builder;
@override
Widget build(BuildContext context) {
// Static colors
if (theme != AppTheme.dynamic) {
final lightTheme = AppTheme.generateThemeData(theme.lightSchema);
final darkTheme = AppTheme.generateThemeData(theme.darkSchema);
return builder(context, lightTheme, darkTheme);
}
// Dynamic color builder
return DynamicColorBuilder(builder: (lightDynamic, darkDynamic) {
final lightTheme =
AppTheme.generateThemeData(lightDynamic ?? theme.lightSchema);
final darkTheme =
AppTheme.generateThemeData(darkDynamic ?? theme.darkSchema);
return builder(context, lightTheme, darkTheme);
});
}
}
@@ -5,17 +5,11 @@ import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/domain/models/render_list.model.dart';
import 'package:immich_mobile/domain/utils/renderlist_providers.dart';
import 'package:immich_mobile/utils/constants/globals.dart';
typedef RenderListProvider = Stream<RenderList> Function();
typedef RenderListAssetProvider = FutureOr<List<Asset>> Function({
int? offset,
int? limit,
});
class AssetGridCubit extends Cubit<RenderList> {
final Stream<RenderList> _renderStream;
final RenderListAssetProvider _assetProvider;
final RenderListProvider _renderListProvider;
late final StreamSubscription _renderListSubscription;
/// offset of the assets from last section in [_buf]
@@ -24,13 +18,11 @@ class AssetGridCubit extends Cubit<RenderList> {
/// assets cache loaded from DB with offset [_bufOffset]
List<Asset> _buf = [];
AssetGridCubit({
required Stream<RenderList> renderStream,
required RenderListAssetProvider assetProvider,
}) : _renderStream = renderStream,
_assetProvider = assetProvider,
AssetGridCubit({required RenderListProvider renderListProvider})
: _renderListProvider = renderListProvider,
super(RenderList.empty()) {
_renderListSubscription = _renderStream.listen((renderList) {
_renderListSubscription =
_renderListProvider.renderStreamProvider().listen((renderList) {
_bufOffset = 0;
_buf = [];
emit(renderList);
@@ -68,7 +60,10 @@ class AssetGridCubit extends Cubit<RenderList> {
);
// load the calculated batch (start:start+len) from the DB and put it into the buffer
_buf = await _assetProvider(offset: start, limit: len);
_buf = await _renderListProvider.renderAssetProvider(
offset: start,
limit: len,
);
_bufOffset = start;
assert(_bufOffset <= offset);
@@ -1,11 +1,9 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/renderlist.interface.dart';
import 'package:immich_mobile/domain/utils/renderlist_providers.dart';
import 'package:immich_mobile/presentation/components/grid/immich_asset_grid.state.dart';
import 'package:immich_mobile/presentation/components/grid/immich_asset_grid.widget.dart';
import 'package:immich_mobile/service_locator.dart';
@RoutePage()
class HomePage extends StatelessWidget {
@@ -16,8 +14,7 @@ class HomePage extends StatelessWidget {
return Scaffold(
body: BlocProvider(
create: (_) => AssetGridCubit(
renderStream: di<IRenderListRepository>().watchAll(),
assetProvider: di<IAssetRepository>().getAll,
renderListProvider: RenderListProvider.mainTimeline(),
),
child: const ImAssetGrid(),
),
@@ -12,7 +12,7 @@ class LoginPageState {
required this.isLoginSuccessful,
});
factory LoginPageState.reset() {
factory LoginPageState.initial() {
return const LoginPageState(
isServerValidated: false,
isValidationInProgress: false,
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:immich_mobile/domain/interfaces/api/user_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
@@ -8,7 +9,6 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/album_sync.service.dart';
import 'package:immich_mobile/domain/services/asset_sync.service.dart';
import 'package:immich_mobile/domain/services/login.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/i18n/strings.g.dart';
import 'package:immich_mobile/presentation/modules/login/models/login_page.model.dart';
import 'package:immich_mobile/presentation/states/gallery_permission.state.dart';
@@ -19,7 +19,7 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:immich_mobile/utils/snackbar_manager.dart';
class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
LoginPageCubit() : super(LoginPageState.reset());
LoginPageCubit() : super(LoginPageState.initial());
String _appendSchema(String url) {
// Add schema if none is set
@@ -68,8 +68,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
url = await loginService.resolveEndpoint(uri);
di<IStoreRepository>().upsert(StoreKey.serverEndpoint, url);
ServiceLocator.registerApiClient(url);
ServiceLocator.registerPostValidationServices();
await ServiceLocator.registerApiClient(url);
ServiceLocator.registerPostGlobalStates();
// Fetch server features
@@ -130,7 +129,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
/// Set token to interceptor
await di<ImApiClient>().init(accessToken: accessToken);
final user = await di<UserService>().getMyUser();
final user = await di<IUserApiRepository>().getMyUser();
if (user == null) {
SnackbarManager.showError(t.login.error.error_login);
return;
@@ -152,6 +151,6 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
}
void resetServerValidation() {
emit(LoginPageState.reset());
emit(LoginPageState.initial());
}
}
@@ -1,22 +0,0 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:immich_mobile/domain/models/app_setting.model.dart';
import 'package:immich_mobile/domain/services/app_setting.service.dart';
import 'package:immich_mobile/presentation/modules/theme/models/app_theme.model.dart';
class AppThemeCubit extends Cubit<AppTheme> {
final AppSettingService _appSettings;
late final StreamSubscription _appSettingSubscription;
AppThemeCubit(this._appSettings) : super(AppTheme.blue) {
_appSettingSubscription =
_appSettings.watch(AppSetting.appTheme).listen((theme) => emit(theme));
}
@override
Future<void> close() {
_appSettingSubscription.cancel();
return super.close();
}
}
@@ -1,43 +0,0 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/presentation/modules/theme/models/app_theme.model.dart';
class AppThemeBuilder extends StatelessWidget {
const AppThemeBuilder({
super.key,
required this.theme,
required this.builder,
});
/// Current app theme to switch the theme data used
final AppTheme theme;
/// Builds the child widget of this widget, providing a light and dark [ThemeData] based on the
/// [theme] passed.
final Widget Function(
BuildContext context,
ThemeData lightTheme,
ThemeData darkTheme,
) builder;
@override
Widget build(BuildContext context) {
// Static colors
if (theme != AppTheme.dynamic) {
final lightTheme = AppTheme.generateThemeData(theme.lightSchema);
final darkTheme = AppTheme.generateThemeData(theme.darkSchema);
return builder(context, lightTheme, darkTheme);
}
// Dynamic color builder
return DynamicColorBuilder(builder: (lightDynamic, darkDynamic) {
final lightTheme =
AppTheme.generateThemeData(lightDynamic ?? theme.lightSchema);
final darkTheme =
AppTheme.generateThemeData(darkDynamic ?? theme.darkSchema);
return builder(context, lightTheme, darkTheme);
});
}
}
@@ -10,6 +10,7 @@ import 'package:immich_mobile/presentation/components/image/immich_logo.widget.d
import 'package:immich_mobile/presentation/modules/login/states/login_page.state.dart';
import 'package:immich_mobile/presentation/router/router.dart';
import 'package:immich_mobile/presentation/states/current_user.state.dart';
import 'package:immich_mobile/presentation/states/gallery_permission.state.dart';
import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
@@ -52,12 +53,13 @@ class _SplashScreenState extends State<SplashScreenPage>
}
Future<void> _tryLogin() async {
await di<GalleryPermissionProvider>().requestPermission();
if (await di<LoginService>().tryAutoLogin() && mounted) {
unawaited(di<AssetSyncService>()
.performFullRemoteSyncIsolate(di<CurrentUserProvider>().value));
unawaited(di<AlbumSyncService>().performFullDeviceSyncIsolate());
unawaited(context.replaceRoute(const TabControllerRoute()));
} else {
} else if (mounted) {
unawaited(context.replaceRoute(const LoginRoute()));
}
}
@@ -0,0 +1,25 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/app_setting.model.dart';
import 'package:immich_mobile/domain/services/app_setting.service.dart';
import 'package:immich_mobile/presentation/theme/app_theme.dart';
class AppThemeProvider extends ValueNotifier<AppTheme> {
final AppSettingService _appSettings;
late final StreamSubscription _appSettingSubscription;
AppThemeProvider({required AppSettingService settingsService})
: _appSettings = settingsService,
super(AppTheme.blue) {
_appSettingSubscription = _appSettings
.watch(AppSetting.appTheme)
.listen((theme) => value = theme);
}
@override
void dispose() {
_appSettingSubscription.cancel();
super.dispose();
}
}
@@ -1,25 +1,26 @@
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/interfaces/api/server_api.interface.dart';
import 'package:immich_mobile/domain/models/server-info/server_feature_config.model.dart';
import 'package:immich_mobile/domain/services/server_info.service.dart';
class ServerFeatureConfigProvider extends ValueNotifier<ServerFeatureConfig> {
final ServerInfoService _serverInfoService;
final IServerApiRepository _serverApiRepository;
ServerFeatureConfigProvider(this._serverInfoService)
: super(const ServerFeatureConfig.reset());
ServerFeatureConfigProvider({required IServerApiRepository serverApiRepo})
: _serverApiRepository = serverApiRepo,
super(const ServerFeatureConfig.initial());
Future<void> getFeatures() async =>
await Future.wait([_getFeatures(), _getConfig()]);
Future<void> _getFeatures() async {
final features = await _serverInfoService.getServerFeatures();
final features = await _serverApiRepository.getServerFeatures();
if (features != null) {
value = value.copyWith(features: features);
}
}
Future<void> _getConfig() async {
final config = await _serverInfoService.getServerConfig();
final config = await _serverApiRepository.getServerConfig();
if (config != null) {
value = value.copyWith(config: config);
}
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/presentation/modules/theme/models/app_colors.model.dart';
import 'package:immich_mobile/presentation/theme/app_colors.dart';
import 'package:immich_mobile/utils/extensions/material_state.extension.dart';
enum AppTheme {
+59 -33
View File
@@ -2,6 +2,10 @@ import 'package:get_it/get_it.dart';
import 'package:immich_mobile/domain/interfaces/album.interface.dart';
import 'package:immich_mobile/domain/interfaces/album_asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/album_etag.interface.dart';
import 'package:immich_mobile/domain/interfaces/api/authentication_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/api/server_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/api/sync_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/api/user_api.interface.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
import 'package:immich_mobile/domain/interfaces/device_album.interface.dart';
@@ -15,6 +19,10 @@ import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/repositories/album.repository.dart';
import 'package:immich_mobile/domain/repositories/album_asset.repository.dart';
import 'package:immich_mobile/domain/repositories/album_etag.repository.dart';
import 'package:immich_mobile/domain/repositories/api/authentication_api.repository.dart';
import 'package:immich_mobile/domain/repositories/api/server_api.repository.dart';
import 'package:immich_mobile/domain/repositories/api/sync_api.repository.dart';
import 'package:immich_mobile/domain/repositories/api/user_api.repository.dart';
import 'package:immich_mobile/domain/repositories/asset.repository.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/domain/repositories/device_album.repository.dart';
@@ -29,11 +37,9 @@ import 'package:immich_mobile/domain/services/app_setting.service.dart';
import 'package:immich_mobile/domain/services/asset_sync.service.dart';
import 'package:immich_mobile/domain/services/hash.service.dart';
import 'package:immich_mobile/domain/services/login.service.dart';
import 'package:immich_mobile/domain/services/server_info.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/platform/messages.g.dart';
import 'package:immich_mobile/presentation/modules/theme/states/app_theme.state.dart';
import 'package:immich_mobile/presentation/router/router.dart';
import 'package:immich_mobile/presentation/states/app_theme.state.dart';
import 'package:immich_mobile/presentation/states/current_user.state.dart';
import 'package:immich_mobile/presentation/states/gallery_permission.state.dart';
import 'package:immich_mobile/presentation/states/server_info/server_feature_config.state.dart';
@@ -68,6 +74,7 @@ class ServiceLocator {
_registerSingleton(DriftDatabaseRepository());
_registerRepositories();
_registerPreGlobalStates();
_registerServices();
}
static void configureServicesForIsolate({
@@ -78,37 +85,63 @@ class ServiceLocator {
_registerSingleton(apiClient);
_registerRepositories();
registerPostValidationServices();
_registerServices();
}
static void _registerRepositories() {
/// Repositories
// Used for transactions
_registerSingleton<IDatabaseRepository>(di<DriftDatabaseRepository>());
_registerFactory<IStoreRepository>(() => StoreRepository(di()));
_registerFactory<ILogRepository>(() => LogRepository(di()));
_registerFactory<AppSettingService>(() => AppSettingService(di()));
_registerFactory<IUserRepository>(() => UserRepository(di()));
_registerFactory<IAssetRepository>(() => AssetRepository(di()));
_registerFactory<IAlbumRepository>(() => AlbumRepository(di()));
_registerSingleton(ImApiClient(endpoint: ''));
_registerFactory<IStoreRepository>(() => StoreRepository(db: di()));
_registerFactory<ILogRepository>(() => LogRepository(db: di()));
_registerFactory<AppSettingService>(() => AppSettingService(store: di()));
_registerFactory<IUserRepository>(() => UserRepository(db: di()));
_registerFactory<IAssetRepository>(() => AssetRepository(db: di()));
_registerFactory<IAlbumRepository>(() => AlbumRepository(db: di()));
_registerFactory<IDeviceAssetRepository>(
() => const DeviceAssetRepository(),
);
_registerFactory<IRenderListRepository>(() => RenderListRepository(di()));
_registerFactory<IRenderListRepository>(
() => RenderListRepository(db: di()),
);
_registerFactory<IDeviceAssetToHashRepository>(
() => DeviceAssetToHashRepository(di()),
() => DeviceAssetToHashRepository(db: di()),
);
_registerFactory<IDeviceAlbumRepository>(
() => const DeviceAlbumRepository(),
);
_registerFactory<IAlbumToAssetRepository>(
() => AlbumToAssetRepository(di()),
() => AlbumToAssetRepository(db: di()),
);
_registerFactory<IAlbumETagRepository>(() => AlbumETagRepository(di()));
/// Services
_registerFactory<LoginService>(() => const LoginService());
/// API Repos
_registerFactory<IAlbumETagRepository>(() => AlbumETagRepository(db: di()));
_registerFactory<ISyncApiRepository>(
() => SyncApiRepository(syncApi: di<ImApiClient>().getSyncApi()),
);
_registerFactory<IServerApiRepository>(
() => ServerApiRepository(serverApi: di<ImApiClient>().getServerApi()),
);
_registerFactory<IAuthenticationApiRepository>(
() => AuthenticationApiRepository(
authenticationApi: di<ImApiClient>().getAuthenticationApi(),
oAuthApi: di<ImApiClient>().getOAuthApi(),
),
);
_registerFactory<IUserApiRepository>(
() => UserApiRepository(usersApi: di<ImApiClient>().getUsersApi()),
);
}
static void _registerServices() {
/// Special services. So they are initiated as singletons
_registerSingleton(ImHostService());
_registerSingleton(const AlbumSyncService());
_registerSingleton(const AssetSyncService());
///
_registerFactory<LoginService>(() => const LoginService());
_registerFactory<HashService>(() => HashService(
hostService: di(),
assetToHashRepo: di(),
@@ -119,30 +152,23 @@ class ServiceLocator {
static void _registerPreGlobalStates() {
_registerSingleton(AppRouter());
_registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
_registerLazySingleton<AppThemeProvider>(
() => AppThemeProvider(settingsService: di()),
);
_registerSingleton(GalleryPermissionProvider());
}
static void registerApiClient(String endpoint) {
_registerSingleton(ImApiClient(endpoint: endpoint));
}
static void registerPostValidationServices() {
_registerFactory<UserService>(() => UserService(
di<ImApiClient>().getUsersApi(),
));
_registerFactory<ServerInfoService>(() => ServerInfoService(
di<ImApiClient>().getServerApi(),
));
_registerSingleton(const AssetSyncService());
}
static void registerPostGlobalStates() {
_registerLazySingleton<ServerFeatureConfigProvider>(
() => ServerFeatureConfigProvider(di()),
() => ServerFeatureConfigProvider(serverApiRepo: di()),
);
}
static Future<void> registerApiClient(String endpoint) async {
await di.unregister<ImApiClient>();
_registerSingleton(ImApiClient(endpoint: endpoint));
}
static void registerCurrentUser(User user) {
_registerSingleton(CurrentUserProvider(user));
}