diff --git a/mobile-v2/build.yaml b/mobile-v2/build.yaml index 04eca9fab7..e6e5fe2f82 100644 --- a/mobile-v2/build.yaml +++ b/mobile-v2/build.yaml @@ -33,6 +33,7 @@ targets: slang_build_runner: options: fallback_strategy: base_locale_empty_string + string_interpolation: braces input_directory: assets/i18n output_directory: lib/i18n timestamp: false diff --git a/mobile-v2/lib/domain/interfaces/api/authentication_api.interface.dart b/mobile-v2/lib/domain/interfaces/api/authentication_api.interface.dart new file mode 100644 index 0000000000..75c8d5d8a9 --- /dev/null +++ b/mobile-v2/lib/domain/interfaces/api/authentication_api.interface.dart @@ -0,0 +1,10 @@ +abstract interface class IAuthenticationApiRepository { + /// Returns the access token on successful login + Future login(String email, String password); + + /// Returns the OAuth URL + Future startOAuth({required String redirectUri}); + + /// Returns the access token on successful oauth login + Future finishOAuth(String url); +} diff --git a/mobile-v2/lib/domain/interfaces/api/server_api.interface.dart b/mobile-v2/lib/domain/interfaces/api/server_api.interface.dart new file mode 100644 index 0000000000..5d3e5e62df --- /dev/null +++ b/mobile-v2/lib/domain/interfaces/api/server_api.interface.dart @@ -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 pingServer(); + + /// Fetches the list of enabled features in the server + Future getServerFeatures(); + + /// Fetches the server configuration and settings + Future getServerConfig(); +} diff --git a/mobile-v2/lib/domain/interfaces/api/sync_api.interface.dart b/mobile-v2/lib/domain/interfaces/api/sync_api.interface.dart new file mode 100644 index 0000000000..8aeb6a5b36 --- /dev/null +++ b/mobile-v2/lib/domain/interfaces/api/sync_api.interface.dart @@ -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?> getFullSyncForUser({ + String? lastId, + required int limit, + required DateTime updatedUntil, + String? userId, + }); +} diff --git a/mobile-v2/lib/domain/interfaces/api/user_api.interface.dart b/mobile-v2/lib/domain/interfaces/api/user_api.interface.dart new file mode 100644 index 0000000000..d710f559d3 --- /dev/null +++ b/mobile-v2/lib/domain/interfaces/api/user_api.interface.dart @@ -0,0 +1,6 @@ +import 'package:immich_mobile/domain/models/user.model.dart'; + +abstract interface class IUserApiRepository { + /// Fetches the current users meta data + Future getMyUser(); +} diff --git a/mobile-v2/lib/domain/models/app_setting.model.dart b/mobile-v2/lib/domain/models/app_setting.model.dart index eed1898a68..aa29dceee2 100644 --- a/mobile-v2/lib/domain/models/app_setting.model.dart +++ b/mobile-v2/lib/domain/models/app_setting.model.dart @@ -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 diff --git a/mobile-v2/lib/domain/models/asset.model.dart b/mobile-v2/lib/domain/models/asset.model.dart index 4b4f5146fd..dbeed82c72 100644 --- a/mobile-v2/lib/domain/models/asset.model.dart +++ b/mobile-v2/lib/domain/models/asset.model.dart @@ -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, - }; diff --git a/mobile-v2/lib/domain/models/server-info/server_config.model.dart b/mobile-v2/lib/domain/models/server-info/server_config.model.dart index a03addb4f7..217b2b03e0 100644 --- a/mobile-v2/lib/domain/models/server-info/server_config.model.dart +++ b/mobile-v2/lib/domain/models/server-info/server_config.model.dart @@ -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() => diff --git a/mobile-v2/lib/domain/models/server-info/server_feature_config.model.dart b/mobile-v2/lib/domain/models/server-info/server_feature_config.model.dart index 63d47d1710..1d83a75627 100644 --- a/mobile-v2/lib/domain/models/server-info/server_feature_config.model.dart +++ b/mobile-v2/lib/domain/models/server-info/server_feature_config.model.dart @@ -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() => diff --git a/mobile-v2/lib/domain/models/server-info/server_features.model.dart b/mobile-v2/lib/domain/models/server-info/server_features.model.dart index a5e0ecd9ee..7913c80933 100644 --- a/mobile-v2/lib/domain/models/server-info/server_features.model.dart +++ b/mobile-v2/lib/domain/models/server-info/server_features.model.dart @@ -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; diff --git a/mobile-v2/lib/domain/models/store.model.dart b/mobile-v2/lib/domain/models/store.model.dart index a60d9fce41..bf5e432463 100644 --- a/mobile-v2/lib/domain/models/store.model.dart +++ b/mobile-v2/lib/domain/models/store.model.dart @@ -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 { diff --git a/mobile-v2/lib/domain/models/user.model.dart b/mobile-v2/lib/domain/models/user.model.dart index 7ddf942731..c9253e61b6 100644 --- a/mobile-v2/lib/domain/models/user.model.dart +++ b/mobile-v2/lib/domain/models/user.model.dart @@ -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) { diff --git a/mobile-v2/lib/domain/repositories/album.repository.dart b/mobile-v2/lib/domain/repositories/album.repository.dart index ab2c3671ad..37792e44ad 100644 --- a/mobile-v2/lib/domain/repositories/album.repository.dart +++ b/mobile-v2/lib/domain/repositories/album.repository.dart @@ -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 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); } diff --git a/mobile-v2/lib/domain/repositories/album_asset.repository.dart b/mobile-v2/lib/domain/repositories/album_asset.repository.dart index 8eac9cf68f..0ea683fb86 100644 --- a/mobile-v2/lib/domain/repositories/album_asset.repository.dart +++ b/mobile-v2/lib/domain/repositories/album_asset.repository.dart @@ -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 addAssetIds(int albumId, Iterable 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], ), diff --git a/mobile-v2/lib/domain/repositories/album_etag.repository.dart b/mobile-v2/lib/domain/repositories/album_etag.repository.dart index 889d19881c..6e11b5e158 100644 --- a/mobile-v2/lib/domain/repositories/album_etag.repository.dart +++ b/mobile-v2/lib/domain/repositories/album_etag.repository.dart @@ -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 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 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(); } } diff --git a/mobile-v2/lib/domain/repositories/api/authentication_api.repository.dart b/mobile-v2/lib/domain/repositories/api/authentication_api.repository.dart new file mode 100644 index 0000000000..831922ec4a --- /dev/null +++ b/mobile-v2/lib/domain/repositories/api/authentication_api.repository.dart @@ -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 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 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 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; + } +} diff --git a/mobile-v2/lib/domain/repositories/api/server_api.repository.dart b/mobile-v2/lib/domain/repositories/api/server_api.repository.dart new file mode 100644 index 0000000000..fbb5203c7d --- /dev/null +++ b/mobile-v2/lib/domain/repositories/api/server_api.repository.dart @@ -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 pingServer() async { + await _serverApi.pingServer(); + } + + @override + Future 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 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, + ); diff --git a/mobile-v2/lib/domain/repositories/api/sync_api.repository.dart b/mobile-v2/lib/domain/repositories/api/sync_api.repository.dart new file mode 100644 index 0000000000..72fcb1ce46 --- /dev/null +++ b/mobile-v2/lib/domain/repositories/api/sync_api.repository.dart @@ -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?> 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, + }; diff --git a/mobile-v2/lib/domain/repositories/api/user_api.repository.dart b/mobile-v2/lib/domain/repositories/api/user_api.repository.dart new file mode 100644 index 0000000000..c716984c5c --- /dev/null +++ b/mobile-v2/lib/domain/repositories/api/user_api.repository.dart @@ -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 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; + } +} diff --git a/mobile-v2/lib/domain/repositories/asset.repository.dart b/mobile-v2/lib/domain/repositories/asset.repository.dart index 8fc58c070c..73ca0a101a 100644 --- a/mobile-v2/lib/domain/repositories/asset.repository.dart +++ b/mobile-v2/lib/domain/repositories/asset.repository.dart @@ -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 upsertAll(Iterable assets) async { diff --git a/mobile-v2/lib/domain/repositories/device_asset_hash.repository.dart b/mobile-v2/lib/domain/repositories/device_asset_hash.repository.dart index d63d91a76b..5d3c873016 100644 --- a/mobile-v2/lib/domain/repositories/device_asset_hash.repository.dart +++ b/mobile-v2/lib/domain/repositories/device_asset_hash.repository.dart @@ -12,7 +12,8 @@ class DeviceAssetToHashRepository implements IDeviceAssetToHashRepository { final DriftDatabaseRepository _db; - const DeviceAssetToHashRepository(this._db); + const DeviceAssetToHashRepository({required DriftDatabaseRepository db}) + : _db = db; @override FutureOr upsertAll(Iterable assetHash) async { @@ -31,10 +32,9 @@ class DeviceAssetToHashRepository @override Future> getForIds(Iterable 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 diff --git a/mobile-v2/lib/domain/repositories/log.repository.dart b/mobile-v2/lib/domain/repositories/log.repository.dart index 0621d11860..fdb26269d0 100644 --- a/mobile-v2/lib/domain/repositories/log.repository.dart +++ b/mobile-v2/lib/domain/repositories/log.repository.dart @@ -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> getAll() async { @@ -32,7 +32,7 @@ class LogRepository implements ILogRepository { @override FutureOr 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 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"); diff --git a/mobile-v2/lib/domain/repositories/renderlist.repository.dart b/mobile-v2/lib/domain/repositories/renderlist.repository.dart index a41dff53b2..f042a93ec6 100644 --- a/mobile-v2/lib/domain/repositories/renderlist.repository.dart +++ b/mobile-v2/lib/domain/repositories/renderlist.repository.dart @@ -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 watchAll() { diff --git a/mobile-v2/lib/domain/repositories/store.repository.dart b/mobile-v2/lib/domain/repositories/store.repository.dart index 117b6a45f2..f14648c55b 100644 --- a/mobile-v2/lib/domain/repositories/store.repository.dart +++ b/mobile-v2/lib/domain/repositories/store.repository.dart @@ -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 tryGet(StoreKey key) async { diff --git a/mobile-v2/lib/domain/repositories/user.repository.dart b/mobile-v2/lib/domain/repositories/user.repository.dart index 07bac4b3ba..ef496896e1 100644 --- a/mobile-v2/lib/domain/repositories/user.repository.dart +++ b/mobile-v2/lib/domain/repositories/user.repository.dart @@ -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 getForId(String userId) async { diff --git a/mobile-v2/lib/domain/services/app_setting.service.dart b/mobile-v2/lib/domain/services/app_setting.service.dart index d7720a93ff..373d6917c7 100644 --- a/mobile-v2/lib/domain/services/app_setting.service.dart +++ b/mobile-v2/lib/domain/services/app_setting.service.dart @@ -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 get(AppSetting setting) async { final value = await _store.tryGet(setting.storeKey); diff --git a/mobile-v2/lib/domain/services/asset_sync.service.dart b/mobile-v2/lib/domain/services/asset_sync.service.dart index 07905088a5..c866c628f3 100644 --- a/mobile-v2/lib/domain/services/asset_sync.service.dart +++ b/mobile-v2/lib/domain/services/asset_sync.service.dart @@ -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().getSyncApi(); final db = di(); final assetRepo = di(); + final syncApiRepo = di(); final chunkSize = limit ?? kFullSyncChunkSize; final updatedTill = updatedUtil ?? DateTime.now().toUtc(); @@ -50,18 +49,16 @@ class AssetSyncService with LogMixin { "Requesting more chunks from lastId - ${lastAssetId ?? ""}", ); - 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; diff --git a/mobile-v2/lib/domain/services/login.service.dart b/mobile-v2/lib/domain/services/login.service.dart index 6dbdfe1365..1e8c85b615 100644 --- a/mobile-v2/lib/domain/services/login.service.dart +++ b/mobile-v2/lib/domain/services/login.service.dart @@ -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 isEndpointAvailable(Uri uri, {ImApiClient? client}) async { + Future 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().pingServer(); } catch (e) { log.e("Exception occured while validating endpoint", e); return false; @@ -61,12 +64,7 @@ class LoginService with LogMixin { Future passwordLogin(String email, String password) async { try { - final loginResponse = - await di().getAuthenticationApi().login( - LoginCredentialDto(email: email, password: password), - ); - - return loginResponse?.accessToken; + return await di().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 oAuthLogin() async { const String oAuthCallbackSchema = 'app.immich'; - - final oAuthApi = di().getOAuthApi(); + final authApi = di(); 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().init(accessToken: accessToken); - final user = await di().getMyUser().timeout( + final user = await di().getMyUser().timeout( const Duration(seconds: 10), // ignore: function-always-returns-null onTimeout: () { diff --git a/mobile-v2/lib/domain/services/server_info.service.dart b/mobile-v2/lib/domain/services/server_info.service.dart deleted file mode 100644 index e76af4324d..0000000000 --- a/mobile-v2/lib/domain/services/server_info.service.dart +++ /dev/null @@ -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 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 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; - } -} diff --git a/mobile-v2/lib/domain/services/user.service.dart b/mobile-v2/lib/domain/services/user.service.dart deleted file mode 100644 index 7c111fe89d..0000000000 --- a/mobile-v2/lib/domain/services/user.service.dart +++ /dev/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 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; - } -} diff --git a/mobile-v2/lib/domain/utils/renderlist_providers.dart b/mobile-v2/lib/domain/utils/renderlist_providers.dart new file mode 100644 index 0000000000..19a33ef1b1 --- /dev/null +++ b/mobile-v2/lib/domain/utils/renderlist_providers.dart @@ -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 Function(); +typedef RenderListAssetProvider = FutureOr> 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().watchAll(), + renderAssetProvider: di().getAll, + ); +} diff --git a/mobile-v2/lib/immich_app.dart b/mobile-v2/lib/immich_app.dart index 647b52d392..bc538ec89b 100644 --- a/mobile-v2/lib/immich_app.dart +++ b/mobile-v2/lib/immich_app.dart @@ -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 with WidgetsBindingObserver { @override Widget build(BuildContext context) { return TranslationProvider( - child: BlocBuilder( - bloc: di(), - builder: (_, appTheme) => AppThemeBuilder( + child: ValueListenableBuilder( + valueListenable: di(), + builder: (_, appTheme, __) => _AppThemeBuilder( theme: appTheme, builder: (ctx, lightTheme, darkTheme) => MaterialApp.router( debugShowCheckedModeBanner: false, @@ -41,3 +40,39 @@ class _ImAppState extends State 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); + }); + } +} diff --git a/mobile-v2/lib/presentation/components/grid/immich_asset_grid.state.dart b/mobile-v2/lib/presentation/components/grid/immich_asset_grid.state.dart index 3797328d6a..cb29fd6a31 100644 --- a/mobile-v2/lib/presentation/components/grid/immich_asset_grid.state.dart +++ b/mobile-v2/lib/presentation/components/grid/immich_asset_grid.state.dart @@ -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 Function(); -typedef RenderListAssetProvider = FutureOr> Function({ - int? offset, - int? limit, -}); - class AssetGridCubit extends Cubit { - final Stream _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 { /// assets cache loaded from DB with offset [_bufOffset] List _buf = []; - AssetGridCubit({ - required Stream 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 { ); // 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); diff --git a/mobile-v2/lib/presentation/modules/home/pages/home.page.dart b/mobile-v2/lib/presentation/modules/home/pages/home.page.dart index d1cb87079f..11157de362 100644 --- a/mobile-v2/lib/presentation/modules/home/pages/home.page.dart +++ b/mobile-v2/lib/presentation/modules/home/pages/home.page.dart @@ -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().watchAll(), - assetProvider: di().getAll, + renderListProvider: RenderListProvider.mainTimeline(), ), child: const ImAssetGrid(), ), diff --git a/mobile-v2/lib/presentation/modules/login/models/login_page.model.dart b/mobile-v2/lib/presentation/modules/login/models/login_page.model.dart index ef51b23806..c15f20db74 100644 --- a/mobile-v2/lib/presentation/modules/login/models/login_page.model.dart +++ b/mobile-v2/lib/presentation/modules/login/models/login_page.model.dart @@ -12,7 +12,7 @@ class LoginPageState { required this.isLoginSuccessful, }); - factory LoginPageState.reset() { + factory LoginPageState.initial() { return const LoginPageState( isServerValidated: false, isValidationInProgress: false, diff --git a/mobile-v2/lib/presentation/modules/login/states/login_page.state.dart b/mobile-v2/lib/presentation/modules/login/states/login_page.state.dart index d61a808400..f27089d1f3 100644 --- a/mobile-v2/lib/presentation/modules/login/states/login_page.state.dart +++ b/mobile-v2/lib/presentation/modules/login/states/login_page.state.dart @@ -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 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 with LogMixin { url = await loginService.resolveEndpoint(uri); di().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 with LogMixin { /// Set token to interceptor await di().init(accessToken: accessToken); - final user = await di().getMyUser(); + final user = await di().getMyUser(); if (user == null) { SnackbarManager.showError(t.login.error.error_login); return; @@ -152,6 +151,6 @@ class LoginPageCubit extends Cubit with LogMixin { } void resetServerValidation() { - emit(LoginPageState.reset()); + emit(LoginPageState.initial()); } } diff --git a/mobile-v2/lib/presentation/modules/theme/states/app_theme.state.dart b/mobile-v2/lib/presentation/modules/theme/states/app_theme.state.dart deleted file mode 100644 index 7bdadf938d..0000000000 --- a/mobile-v2/lib/presentation/modules/theme/states/app_theme.state.dart +++ /dev/null @@ -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 { - final AppSettingService _appSettings; - late final StreamSubscription _appSettingSubscription; - - AppThemeCubit(this._appSettings) : super(AppTheme.blue) { - _appSettingSubscription = - _appSettings.watch(AppSetting.appTheme).listen((theme) => emit(theme)); - } - - @override - Future close() { - _appSettingSubscription.cancel(); - return super.close(); - } -} diff --git a/mobile-v2/lib/presentation/modules/theme/widgets/app_theme_builder.widget.dart b/mobile-v2/lib/presentation/modules/theme/widgets/app_theme_builder.widget.dart deleted file mode 100644 index a06fac41b9..0000000000 --- a/mobile-v2/lib/presentation/modules/theme/widgets/app_theme_builder.widget.dart +++ /dev/null @@ -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); - }); - } -} diff --git a/mobile-v2/lib/presentation/router/pages/splash_screen.page.dart b/mobile-v2/lib/presentation/router/pages/splash_screen.page.dart index 7de2f5c490..b451a15832 100644 --- a/mobile-v2/lib/presentation/router/pages/splash_screen.page.dart +++ b/mobile-v2/lib/presentation/router/pages/splash_screen.page.dart @@ -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 } Future _tryLogin() async { + await di().requestPermission(); if (await di().tryAutoLogin() && mounted) { unawaited(di() .performFullRemoteSyncIsolate(di().value)); unawaited(di().performFullDeviceSyncIsolate()); unawaited(context.replaceRoute(const TabControllerRoute())); - } else { + } else if (mounted) { unawaited(context.replaceRoute(const LoginRoute())); } } diff --git a/mobile-v2/lib/presentation/states/app_theme.state.dart b/mobile-v2/lib/presentation/states/app_theme.state.dart new file mode 100644 index 0000000000..285b0f5cda --- /dev/null +++ b/mobile-v2/lib/presentation/states/app_theme.state.dart @@ -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 { + 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(); + } +} diff --git a/mobile-v2/lib/presentation/states/server_info/server_feature_config.state.dart b/mobile-v2/lib/presentation/states/server_info/server_feature_config.state.dart index e45e316a13..ed2f38a3b1 100644 --- a/mobile-v2/lib/presentation/states/server_info/server_feature_config.state.dart +++ b/mobile-v2/lib/presentation/states/server_info/server_feature_config.state.dart @@ -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 { - final ServerInfoService _serverInfoService; + final IServerApiRepository _serverApiRepository; - ServerFeatureConfigProvider(this._serverInfoService) - : super(const ServerFeatureConfig.reset()); + ServerFeatureConfigProvider({required IServerApiRepository serverApiRepo}) + : _serverApiRepository = serverApiRepo, + super(const ServerFeatureConfig.initial()); Future getFeatures() async => await Future.wait([_getFeatures(), _getConfig()]); Future _getFeatures() async { - final features = await _serverInfoService.getServerFeatures(); + final features = await _serverApiRepository.getServerFeatures(); if (features != null) { value = value.copyWith(features: features); } } Future _getConfig() async { - final config = await _serverInfoService.getServerConfig(); + final config = await _serverApiRepository.getServerConfig(); if (config != null) { value = value.copyWith(config: config); } diff --git a/mobile-v2/lib/presentation/modules/theme/models/app_colors.model.dart b/mobile-v2/lib/presentation/theme/app_colors.dart similarity index 100% rename from mobile-v2/lib/presentation/modules/theme/models/app_colors.model.dart rename to mobile-v2/lib/presentation/theme/app_colors.dart diff --git a/mobile-v2/lib/presentation/modules/theme/models/app_theme.model.dart b/mobile-v2/lib/presentation/theme/app_theme.dart similarity index 97% rename from mobile-v2/lib/presentation/modules/theme/models/app_theme.model.dart rename to mobile-v2/lib/presentation/theme/app_theme.dart index c29675b2a7..212832835c 100644 --- a/mobile-v2/lib/presentation/modules/theme/models/app_theme.model.dart +++ b/mobile-v2/lib/presentation/theme/app_theme.dart @@ -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 { diff --git a/mobile-v2/lib/service_locator.dart b/mobile-v2/lib/service_locator.dart index 4210f6f7c6..0b58b83936 100644 --- a/mobile-v2/lib/service_locator.dart +++ b/mobile-v2/lib/service_locator.dart @@ -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(di()); - _registerFactory(() => StoreRepository(di())); - _registerFactory(() => LogRepository(di())); - _registerFactory(() => AppSettingService(di())); - _registerFactory(() => UserRepository(di())); - _registerFactory(() => AssetRepository(di())); - _registerFactory(() => AlbumRepository(di())); + _registerSingleton(ImApiClient(endpoint: '')); + + _registerFactory(() => StoreRepository(db: di())); + _registerFactory(() => LogRepository(db: di())); + _registerFactory(() => AppSettingService(store: di())); + _registerFactory(() => UserRepository(db: di())); + _registerFactory(() => AssetRepository(db: di())); + _registerFactory(() => AlbumRepository(db: di())); _registerFactory( () => const DeviceAssetRepository(), ); - _registerFactory(() => RenderListRepository(di())); + _registerFactory( + () => RenderListRepository(db: di()), + ); _registerFactory( - () => DeviceAssetToHashRepository(di()), + () => DeviceAssetToHashRepository(db: di()), ); _registerFactory( () => const DeviceAlbumRepository(), ); _registerFactory( - () => AlbumToAssetRepository(di()), + () => AlbumToAssetRepository(db: di()), ); - _registerFactory(() => AlbumETagRepository(di())); - /// Services - _registerFactory(() => const LoginService()); + /// API Repos + _registerFactory(() => AlbumETagRepository(db: di())); + _registerFactory( + () => SyncApiRepository(syncApi: di().getSyncApi()), + ); + _registerFactory( + () => ServerApiRepository(serverApi: di().getServerApi()), + ); + _registerFactory( + () => AuthenticationApiRepository( + authenticationApi: di().getAuthenticationApi(), + oAuthApi: di().getOAuthApi(), + ), + ); + _registerFactory( + () => UserApiRepository(usersApi: di().getUsersApi()), + ); + } + + static void _registerServices() { + /// Special services. So they are initiated as singletons _registerSingleton(ImHostService()); _registerSingleton(const AlbumSyncService()); + _registerSingleton(const AssetSyncService()); + + /// + _registerFactory(() => const LoginService()); _registerFactory(() => HashService( hostService: di(), assetToHashRepo: di(), @@ -119,30 +152,23 @@ class ServiceLocator { static void _registerPreGlobalStates() { _registerSingleton(AppRouter()); - _registerLazySingleton(() => AppThemeCubit(di())); + _registerLazySingleton( + () => AppThemeProvider(settingsService: di()), + ); _registerSingleton(GalleryPermissionProvider()); } - static void registerApiClient(String endpoint) { - _registerSingleton(ImApiClient(endpoint: endpoint)); - } - - static void registerPostValidationServices() { - _registerFactory(() => UserService( - di().getUsersApi(), - )); - _registerFactory(() => ServerInfoService( - di().getServerApi(), - )); - _registerSingleton(const AssetSyncService()); - } - static void registerPostGlobalStates() { _registerLazySingleton( - () => ServerFeatureConfigProvider(di()), + () => ServerFeatureConfigProvider(serverApiRepo: di()), ); } + static Future registerApiClient(String endpoint) async { + await di.unregister(); + _registerSingleton(ImApiClient(endpoint: endpoint)); + } + static void registerCurrentUser(User user) { _registerSingleton(CurrentUserProvider(user)); } diff --git a/mobile-v2/pubspec.lock b/mobile-v2/pubspec.lock index 765b3a399e..1f6d100d17 100644 --- a/mobile-v2/pubspec.lock +++ b/mobile-v2/pubspec.lock @@ -114,10 +114,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.12" + version: "2.4.13" build_runner_core: dependency: transitive description: @@ -270,14 +270,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + desktop_webview_window: + dependency: transitive + description: + name: desktop_webview_window + sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" + url: "https://pub.dev" + source: hosted + version: "0.2.3" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + sha256: c4af09051b4f0508f6c1dc0a5c085bf014d5c9a4a0678ce1799c2b4d716387a0 url: "https://pub.dev" source: hosted - version: "10.1.2" + version: "11.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -290,26 +298,26 @@ packages: dependency: "direct main" description: name: drift - sha256: "5b561ec76fff260e1e0593a29ca0d058a140a4b4dfb11dcc0c3813820cd20200" + sha256: df027d168a2985a2e9da900adeba2ab0136f0d84436592cf3cd5135f82c8579c url: "https://pub.dev" source: hosted - version: "2.20.2" + version: "2.21.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "3ee987578ca2281b5ff91eadd757cd6dd36001458d6e33784f990d67ff38f756" + sha256: "27bab15e7869b69259663590381180117873b9b273a1ea9ebb21bb73133d1233" url: "https://pub.dev" source: hosted - version: "2.20.3" + version: "2.21.0" drift_flutter: dependency: "direct main" description: name: drift_flutter - sha256: c670c947fe17ad149678a43fdbbfdb69321f0c83d315043e34e8ad2729e11f49 + sha256: fec503e9d408f36bb345f9f6d24bc9d62b7b5f970db49760253d9e8d3acd48d5 url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" dynamic_color: dependency: "direct main" description: @@ -383,26 +391,26 @@ packages: dependency: transitive description: name: flutter_gen_core - sha256: "638d518897f1aefc55a24278968027591d50223a6943b6ae9aa576fe1494d99d" + sha256: "46ecf0e317413dd065547887c43f93f55e9653e83eb98dc13dd07d40dd225325" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0" flutter_gen_runner: dependency: "direct dev" description: name: flutter_gen_runner - sha256: "7f2f02d95e3ec96cf70a1c515700c0dd3ea905af003303a55d6fb081240e6b8a" + sha256: "77f0a02fc30d9fcf2549fe874eb3fde091435724904bcbb1af60aa40cbfab1f4" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" flutter_list_view: dependency: "direct main" description: @@ -425,18 +433,18 @@ packages: dependency: "direct main" description: name: flutter_web_auth_2 - sha256: "4d3d2fd3d26bf1a26b3beafd4b4b899c0ffe10dc99af25abc58ffe24e991133c" + sha256: "8f59c9fa71b5affb322cb7103b836cd0ced89c9c50c66f82b523b7d339018dc3" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.0.1" flutter_web_auth_2_platform_interface: dependency: transitive description: name: flutter_web_auth_2_platform_interface - sha256: e8669e262005a8354389ba2971f0fc1c36188481234ff50d013aaf993f30f739 + sha256: "222264d4979e9372c90e441736a62d800481e4a9c860cc2c235d1d605a118a2b" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.1" flutter_web_plugins: dependency: transitive description: flutter @@ -454,10 +462,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + sha256: ff97e5e7b2e82e63c82f5658c6ba2605ea831f0f7489b0d2fb255d817ec4eb5e url: "https://pub.dev" source: hosted - version: "7.7.0" + version: "8.0.0" glob: dependency: transitive description: @@ -582,18 +590,18 @@ packages: dependency: transitive description: name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" logging: dependency: "direct main" description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" macros: dependency: transitive description: @@ -622,10 +630,10 @@ packages: dependency: "direct main" description: name: material_symbols_icons - sha256: "66416c4e30bd363508e12669634fc4f3250b83b69e862de67f4f9c480cf42414" + sha256: "7626ce90395bc6dc2ecb7bdd84c04a97f3f084a4e923ff73791c3c409af02804" url: "https://pub.dev" source: hosted - version: "4.2785.1" + version: "4.2789.0" meta: dependency: transitive description: @@ -677,10 +685,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998 url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -813,26 +821,26 @@ packages: dependency: "direct main" description: name: photo_manager - sha256: e29619443803c40385ee509abc7937835d9b5122f899940080d28b2dceed59c1 + sha256: "70159eee32203e8162d49d588232f0299ed3f383c63eef1e899cb6b83dee6b26" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.5.1" photo_manager_image_provider: dependency: "direct main" description: name: photo_manager_image_provider - sha256: "38ef1023dc11de3a8669f16e7c981673b3c5cfee715d17120f4b87daa2cdd0af" + sha256: b6015b67b32f345f57cf32c126f871bced2501236c405aafaefa885f7c821e4f url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" pigeon: dependency: "direct dev" description: name: pigeon - sha256: "95481446c02fa79fcf0e8014882f8a3b87fd06c257e9e1c3d4cc6d102a925ad8" + sha256: "2f7af49f530b3208131489ce601f8d95d567b3fa3c71265a87f62b0b87d41e91" url: "https://pub.dev" source: hosted - version: "22.4.0" + version: "22.5.0" platform: dependency: transitive description: @@ -922,26 +930,26 @@ packages: dependency: "direct main" description: name: slang - sha256: a2f704508bf9f209b71c881347bd27de45309651e9bd63570e4dd6ed2a77fbd2 + sha256: a466773de768eb95bdf681e0a92e7c8010d44bb247b62130426c83ece33aeaed url: "https://pub.dev" source: hosted - version: "3.31.2" + version: "3.32.0" slang_build_runner: dependency: "direct dev" description: name: slang_build_runner - sha256: "6e60160e8000b91824c47221b20d9642e7408287a5a21837ecefc75270197586" + sha256: b2e0c63f3c801a4aa70b4ca43173893d6eb7d5a421fc9d97ad983527397631b3 url: "https://pub.dev" source: hosted - version: "3.31.0" + version: "3.32.0" slang_flutter: dependency: "direct main" description: name: slang_flutter - sha256: f8400292be49c11697d94af58d7f7d054c91af759f41ffe71e4e5413871ffc62 + sha256: "1a98e878673996902fa5ef0b61ce5c245e41e4d25640d18af061c6aab917b0c7" url: "https://pub.dev" source: hosted - version: "3.31.0" + version: "3.32.0" source_gen: dependency: transitive description: @@ -1002,10 +1010,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: "852cf80f9e974ac8e1b613758a8aa640215f7701352b66a7f468e95711eb570b" + sha256: c5f63dff8677407ddcddfa4744c176ea6dc44286c47ba9e69e76d8071398034d url: "https://pub.dev" source: hosted - version: "0.38.1" + version: "0.39.1" stack_trace: dependency: transitive description: @@ -1090,10 +1098,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: diff --git a/mobile-v2/pubspec.yaml b/mobile-v2/pubspec.yaml index 0039797504..633e559ee4 100644 --- a/mobile-v2/pubspec.yaml +++ b/mobile-v2/pubspec.yaml @@ -1,11 +1,11 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone -publish_to: "none" +publish_to: 'none' version: 1.102.0+132 environment: - sdk: ">=3.3.3 <4.0.0" + sdk: '>=3.3.3 <4.0.0' dependencies: flutter: @@ -17,15 +17,15 @@ dependencies: path_provider: ^2.1.4 path: ^1.9.0 dynamic_color: ^1.7.0 - url_launcher: ^6.3.0 - package_info_plus: ^8.0.2 - device_info_plus: ^10.1.2 + url_launcher: ^6.3.1 + package_info_plus: ^8.1.0 + device_info_plus: ^11.1.0 permission_handler: ^11.3.1 # State handling flutter_bloc: ^8.1.6 # Database - drift: ^2.20.2 - drift_flutter: ^0.2.0 + drift: ^2.21.0 + drift_flutter: ^0.2.1 sqlite3: ^2.4.6 sqlite3_flutter_libs: ^0.5.24 # Network @@ -33,23 +33,23 @@ dependencies: # Route handling auto_route: ^9.2.2 # Logging - logging: ^1.2.0 + logging: ^1.3.0 # Collection Utils collection: ^1.18.0 # service_locator - get_it: ^7.7.0 + get_it: ^8.0.0 # Photo Manager - photo_manager: ^3.3.0 - photo_manager_image_provider: ^2.1.1 + photo_manager: ^3.5.1 + photo_manager_image_provider: ^2.2.0 # Localization intl: ^0.19.0 - slang: ^3.31.2 - slang_flutter: ^3.31.0 + slang: ^3.32.0 + slang_flutter: ^3.32.0 # oauth login - flutter_web_auth_2: ^3.1.2 + flutter_web_auth_2: ^4.0.1 # components octo_image: ^2.1.0 - material_symbols_icons: ^4.2785.1 + material_symbols_icons: ^4.2789.0 flutter_adaptive_scaffold: ^0.3.1 flutter_list_view: ^1.1.28 cached_network_image: ^3.4.1 @@ -63,19 +63,19 @@ dev_dependencies: sdk: flutter # Recommended lints - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 # Code generator - build_runner: ^2.4.12 + build_runner: ^2.4.13 # Database helper - drift_dev: ^2.20.3 + drift_dev: ^2.21.0 # Route helper auto_route_generator: ^9.0.0 # Localization generator - slang_build_runner: ^3.31.0 + slang_build_runner: ^3.32.0 # Assets constant generator - flutter_gen_runner: ^5.7.0 + flutter_gen_runner: ^5.8.0 # Type safe platform channels - pigeon: ^22.4.0 + pigeon: ^22.5.0 flutter: uses-material-design: true