mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
more refactors
This commit is contained in:
parent
e8bb9a3934
commit
3b8951fde6
@ -33,6 +33,7 @@ targets:
|
|||||||
slang_build_runner:
|
slang_build_runner:
|
||||||
options:
|
options:
|
||||||
fallback_strategy: base_locale_empty_string
|
fallback_strategy: base_locale_empty_string
|
||||||
|
string_interpolation: braces
|
||||||
input_directory: assets/i18n
|
input_directory: assets/i18n
|
||||||
output_directory: lib/i18n
|
output_directory: lib/i18n
|
||||||
timestamp: false
|
timestamp: false
|
||||||
|
@ -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();
|
||||||
|
}
|
11
mobile-v2/lib/domain/interfaces/api/sync_api.interface.dart
Normal file
11
mobile-v2/lib/domain/interfaces/api/sync_api.interface.dart
Normal file
@ -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:flutter/material.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/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
|
// 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
|
// 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:flutter/material.dart';
|
||||||
import 'package:immich_mobile/utils/collection_util.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 {
|
enum AssetType {
|
||||||
// do not change this order!
|
// do not change this order!
|
||||||
@ -49,19 +47,6 @@ class Asset {
|
|||||||
this.livePhotoVideoId,
|
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({
|
Asset copyWith({
|
||||||
int? id,
|
int? id,
|
||||||
String? name,
|
String? name,
|
||||||
@ -177,10 +162,3 @@ class Asset {
|
|||||||
static int compareByLocalId(Asset a, Asset b) =>
|
static int compareByLocalId(Asset a, Asset b) =>
|
||||||
CollectionUtil.compareToNullable(a.localId, b.localId);
|
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 {
|
class ServerConfig {
|
||||||
final String? oauthButtonText;
|
final String? oauthButtonText;
|
||||||
|
|
||||||
@ -11,12 +9,7 @@ class ServerConfig {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ServerConfig.fromDto(ServerConfigDto dto) => ServerConfig(
|
const ServerConfig.initial() : oauthButtonText = null;
|
||||||
oauthButtonText:
|
|
||||||
dto.oauthButtonText.isEmpty ? null : dto.oauthButtonText,
|
|
||||||
);
|
|
||||||
|
|
||||||
const ServerConfig.reset() : oauthButtonText = null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
|
@ -17,9 +17,9 @@ class ServerFeatureConfig {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ServerFeatureConfig.reset()
|
const ServerFeatureConfig.initial()
|
||||||
: features = const ServerFeatures.reset(),
|
: features = const ServerFeatures.initial(),
|
||||||
config = const ServerConfig.reset();
|
config = const ServerConfig.initial();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'package:openapi/api.dart';
|
|
||||||
|
|
||||||
class ServerFeatures {
|
class ServerFeatures {
|
||||||
final bool hasPasswordLogin;
|
final bool hasPasswordLogin;
|
||||||
final bool hasOAuthLogin;
|
final bool hasOAuthLogin;
|
||||||
@ -16,12 +14,7 @@ class ServerFeatures {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ServerFeatures.fromDto(ServerFeaturesDto dto) => ServerFeatures(
|
const ServerFeatures.initial()
|
||||||
hasPasswordLogin: dto.passwordLogin,
|
|
||||||
hasOAuthLogin: dto.oauth,
|
|
||||||
);
|
|
||||||
|
|
||||||
const ServerFeatures.reset()
|
|
||||||
: hasPasswordLogin = true,
|
: hasPasswordLogin = true,
|
||||||
hasOAuthLogin = false;
|
hasOAuthLogin = false;
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/store_converters.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
|
@immutable
|
||||||
class StoreValue<T> {
|
class StoreValue<T> {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:openapi/api.dart' as api;
|
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
const User({
|
const User({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -96,25 +94,6 @@ class User {
|
|||||||
memoryEnabled.hashCode ^
|
memoryEnabled.hashCode ^
|
||||||
avatarColor.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 {
|
enum UserAvatarColor {
|
||||||
@ -131,34 +110,6 @@ enum UserAvatarColor {
|
|||||||
amber,
|
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 {
|
extension AvatarColorToColorHelper on UserAvatarColor {
|
||||||
Color toColor([bool isDarkTheme = false]) {
|
Color toColor([bool isDarkTheme = false]) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
@ -10,16 +10,16 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
|
|||||||
class AlbumRepository with LogMixin implements IAlbumRepository {
|
class AlbumRepository with LogMixin implements IAlbumRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const AlbumRepository(this._db);
|
const AlbumRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<Album?> upsert(Album album) async {
|
FutureOr<Album?> upsert(Album album) async {
|
||||||
try {
|
try {
|
||||||
final albumData = _toEntity(album);
|
final albumData = _toEntity(album);
|
||||||
final data = await _db.into(_db.album).insertReturningOrNull(
|
final data = await _db.album.insertReturningOrNull(
|
||||||
albumData,
|
albumData,
|
||||||
onConflict: DoUpdate((_) => albumData, target: [_db.album.localId]),
|
onConflict: DoUpdate((_) => albumData, target: [_db.album.localId]),
|
||||||
);
|
);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
return _toModel(data);
|
return _toModel(data);
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,16 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
|
|||||||
class AlbumToAssetRepository with LogMixin implements IAlbumToAssetRepository {
|
class AlbumToAssetRepository with LogMixin implements IAlbumToAssetRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const AlbumToAssetRepository(this._db);
|
const AlbumToAssetRepository({required DriftDatabaseRepository db})
|
||||||
|
: _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> addAssetIds(int albumId, Iterable<int> assetIds) async {
|
FutureOr<bool> addAssetIds(int albumId, Iterable<int> assetIds) async {
|
||||||
try {
|
try {
|
||||||
await _db.albumToAsset.insertAll(
|
await _db.albumToAsset.insertAll(
|
||||||
assetIds.map((a) => AlbumToAssetCompanion.insert(
|
assetIds.map(
|
||||||
assetId: a,
|
(a) => AlbumToAssetCompanion.insert(assetId: a, albumId: albumId),
|
||||||
albumId: albumId,
|
),
|
||||||
)),
|
|
||||||
onConflict: DoNothing(
|
onConflict: DoNothing(
|
||||||
target: [_db.albumToAsset.assetId, _db.albumToAsset.albumId],
|
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 {
|
class AlbumETagRepository with LogMixin implements IAlbumETagRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const AlbumETagRepository(this._db);
|
const AlbumETagRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> upsert(AlbumETag albumETag) async {
|
FutureOr<bool> upsert(AlbumETag albumETag) async {
|
||||||
try {
|
try {
|
||||||
final entity = _toEntity(albumETag);
|
final entity = _toEntity(albumETag);
|
||||||
await _db.into(_db.albumETag).insert(
|
await _db.albumETag.insertOne(
|
||||||
entity,
|
entity,
|
||||||
onConflict:
|
onConflict: DoUpdate((_) => entity, target: [_db.albumETag.albumId]),
|
||||||
DoUpdate((_) => entity, target: [_db.albumETag.albumId]),
|
);
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log.e("Error while adding an album etag to the DB", 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
|
@override
|
||||||
FutureOr<AlbumETag?> get(int albumId) async {
|
FutureOr<AlbumETag?> get(int albumId) async {
|
||||||
return await _db.managers.albumETag
|
final query = _db.albumETag.select()
|
||||||
.filter((r) => r.albumId.id.equals(albumId))
|
..where((r) => r.albumId.equals(albumId));
|
||||||
.map(_toModel)
|
return await query.map(_toModel).getSingleOrNull();
|
||||||
.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 {
|
class AssetRepository with LogMixin implements IAssetRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const AssetRepository(this._db);
|
const AssetRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> upsertAll(Iterable<Asset> assets) async {
|
Future<bool> upsertAll(Iterable<Asset> assets) async {
|
||||||
|
@ -12,7 +12,8 @@ class DeviceAssetToHashRepository
|
|||||||
implements IDeviceAssetToHashRepository {
|
implements IDeviceAssetToHashRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const DeviceAssetToHashRepository(this._db);
|
const DeviceAssetToHashRepository({required DriftDatabaseRepository db})
|
||||||
|
: _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> upsertAll(Iterable<DeviceAssetToHash> assetHash) async {
|
FutureOr<bool> upsertAll(Iterable<DeviceAssetToHash> assetHash) async {
|
||||||
@ -31,10 +32,9 @@ class DeviceAssetToHashRepository
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<DeviceAssetToHash>> getForIds(Iterable<String> localIds) async {
|
Future<List<DeviceAssetToHash>> getForIds(Iterable<String> localIds) async {
|
||||||
return await _db.managers.deviceAssetToHash
|
final query = _db.deviceAssetToHash.select()
|
||||||
.filter((f) => f.localId.isIn(localIds))
|
..where((f) => f.localId.isIn(localIds));
|
||||||
.map(_toModel)
|
return await query.map(_toModel).get();
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -10,7 +10,7 @@ import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
|||||||
class LogRepository implements ILogRepository {
|
class LogRepository implements ILogRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const LogRepository(this._db);
|
const LogRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<LogMessage>> getAll() async {
|
Future<List<LogMessage>> getAll() async {
|
||||||
@ -32,7 +32,7 @@ class LogRepository implements ILogRepository {
|
|||||||
@override
|
@override
|
||||||
FutureOr<bool> create(LogMessage log) async {
|
FutureOr<bool> create(LogMessage log) async {
|
||||||
try {
|
try {
|
||||||
await _db.into(_db.logs).insert(_toEntity(log));
|
await _db.logs.insertOne(_toEntity(log));
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error while adding a log to the DB - $e");
|
debugPrint("Error while adding a log to the DB - $e");
|
||||||
@ -56,7 +56,7 @@ class LogRepository implements ILogRepository {
|
|||||||
@override
|
@override
|
||||||
FutureOr<bool> deleteAll() async {
|
FutureOr<bool> deleteAll() async {
|
||||||
try {
|
try {
|
||||||
await _db.managers.logs.delete();
|
await _db.logs.deleteAll();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error while clearning the logs in DB - $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 {
|
class RenderListRepository with LogMixin implements IRenderListRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const RenderListRepository(this._db);
|
const RenderListRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<RenderList> watchAll() {
|
Stream<RenderList> watchAll() {
|
||||||
|
@ -10,7 +10,7 @@ import 'package:immich_mobile/utils/mixins/log.mixin.dart';
|
|||||||
class StoreRepository with LogMixin implements IStoreRepository {
|
class StoreRepository with LogMixin implements IStoreRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const StoreRepository(this._db);
|
const StoreRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key) async {
|
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 {
|
class UserRepository with LogMixin implements IUserRepository {
|
||||||
final DriftDatabaseRepository _db;
|
final DriftDatabaseRepository _db;
|
||||||
|
|
||||||
const UserRepository(this._db);
|
const UserRepository({required DriftDatabaseRepository db}) : _db = db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<User?> getForId(String userId) async {
|
FutureOr<User?> getForId(String userId) async {
|
||||||
|
@ -4,7 +4,7 @@ import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
|||||||
class AppSettingService {
|
class AppSettingService {
|
||||||
final IStoreRepository _store;
|
final IStoreRepository _store;
|
||||||
|
|
||||||
const AppSettingService(this._store);
|
const AppSettingService({required IStoreRepository store}) : _store = store;
|
||||||
|
|
||||||
Future<T> get<T>(AppSetting<T> setting) async {
|
Future<T> get<T>(AppSetting<T> setting) async {
|
||||||
final value = await _store.tryGet(setting.storeKey);
|
final value = await _store.tryGet(setting.storeKey);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
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/asset.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset.model.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/service_locator.dart';
|
||||||
import 'package:immich_mobile/utils/collection_util.dart';
|
import 'package:immich_mobile/utils/collection_util.dart';
|
||||||
import 'package:immich_mobile/utils/constants/globals.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/isolate_helper.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
|
|
||||||
class AssetSyncService with LogMixin {
|
class AssetSyncService with LogMixin {
|
||||||
const AssetSyncService();
|
const AssetSyncService();
|
||||||
@ -36,9 +35,9 @@ class AssetSyncService with LogMixin {
|
|||||||
int? limit,
|
int? limit,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final syncClient = di<ImApiClient>().getSyncApi();
|
|
||||||
final db = di<IDatabaseRepository>();
|
final db = di<IDatabaseRepository>();
|
||||||
final assetRepo = di<IAssetRepository>();
|
final assetRepo = di<IAssetRepository>();
|
||||||
|
final syncApiRepo = di<ISyncApiRepository>();
|
||||||
|
|
||||||
final chunkSize = limit ?? kFullSyncChunkSize;
|
final chunkSize = limit ?? kFullSyncChunkSize;
|
||||||
final updatedTill = updatedUtil ?? DateTime.now().toUtc();
|
final updatedTill = updatedUtil ?? DateTime.now().toUtc();
|
||||||
@ -50,18 +49,16 @@ class AssetSyncService with LogMixin {
|
|||||||
"Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}",
|
"Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}",
|
||||||
);
|
);
|
||||||
|
|
||||||
final assets = await syncClient.getFullSyncForUser(AssetFullSyncDto(
|
final assetsFromServer = await syncApiRepo.getFullSyncForUser(
|
||||||
limit: chunkSize,
|
limit: chunkSize,
|
||||||
updatedUntil: updatedTill,
|
updatedUntil: updatedTill,
|
||||||
lastId: lastAssetId,
|
lastId: lastAssetId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
));
|
);
|
||||||
if (assets == null) {
|
if (assetsFromServer == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
final assetsFromServer = assets.map(Asset.remote).toList();
|
|
||||||
|
|
||||||
await db.txn(() async {
|
await db.txn(() async {
|
||||||
final assetsInDb =
|
final assetsInDb =
|
||||||
await assetRepo.getForHashes(assetsFromServer.map((a) => a.hash));
|
await assetRepo.getForHashes(assetsFromServer.map((a) => a.hash));
|
||||||
@ -73,8 +70,8 @@ class AssetSyncService with LogMixin {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
lastAssetId = assets.lastOrNull?.id;
|
lastAssetId = assetsFromServer.lastOrNull?.remoteId;
|
||||||
if (assets.length != chunkSize) break;
|
if (assetsFromServer.length != chunkSize) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -4,28 +4,31 @@ import 'dart:io';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||||
import 'package:http/http.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/interfaces/store.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
import 'package:immich_mobile/utils/immich_api_client.dart';
|
import 'package:immich_mobile/utils/immich_api_client.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log.mixin.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 {
|
class LoginService with LogMixin {
|
||||||
const LoginService();
|
const LoginService();
|
||||||
|
|
||||||
Future<bool> isEndpointAvailable(Uri uri, {ImApiClient? client}) async {
|
Future<bool> isEndpointAvailable(Uri uri) async {
|
||||||
String baseUrl = uri.toString();
|
String baseUrl = uri.toString();
|
||||||
|
|
||||||
if (!baseUrl.endsWith('/api')) {
|
if (!baseUrl.endsWith('/api')) {
|
||||||
baseUrl += '/api';
|
baseUrl += '/api';
|
||||||
}
|
}
|
||||||
|
|
||||||
final serverAPI =
|
await ServiceLocator.registerApiClient(baseUrl);
|
||||||
client?.getServerApi() ?? ImApiClient(endpoint: baseUrl).getServerApi();
|
|
||||||
try {
|
try {
|
||||||
await serverAPI.pingServer();
|
await di<IServerApiRepository>().pingServer();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.e("Exception occured while validating endpoint", e);
|
log.e("Exception occured while validating endpoint", e);
|
||||||
return false;
|
return false;
|
||||||
@ -61,12 +64,7 @@ class LoginService with LogMixin {
|
|||||||
|
|
||||||
Future<String?> passwordLogin(String email, String password) async {
|
Future<String?> passwordLogin(String email, String password) async {
|
||||||
try {
|
try {
|
||||||
final loginResponse =
|
return await di<IAuthenticationApiRepository>().login(email, password);
|
||||||
await di<ImApiClient>().getAuthenticationApi().login(
|
|
||||||
LoginCredentialDto(email: email, password: password),
|
|
||||||
);
|
|
||||||
|
|
||||||
return loginResponse?.accessToken;
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log.e("Exception occured while performing password login", e, s);
|
log.e("Exception occured while performing password login", e, s);
|
||||||
}
|
}
|
||||||
@ -75,16 +73,14 @@ class LoginService with LogMixin {
|
|||||||
|
|
||||||
Future<String?> oAuthLogin() async {
|
Future<String?> oAuthLogin() async {
|
||||||
const String oAuthCallbackSchema = 'app.immich';
|
const String oAuthCallbackSchema = 'app.immich';
|
||||||
|
final authApi = di<IAuthenticationApiRepository>();
|
||||||
final oAuthApi = di<ImApiClient>().getOAuthApi();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final oAuthUrl = await oAuthApi.startOAuth(
|
final oAuthUrl = await authApi.startOAuth(
|
||||||
OAuthConfigDto(redirectUri: "$oAuthCallbackSchema:/"),
|
redirectUri: "$oAuthCallbackSchema:/",
|
||||||
);
|
);
|
||||||
|
|
||||||
final oAuthUrlRes = oAuthUrl?.url;
|
if (oAuthUrl == null) {
|
||||||
if (oAuthUrlRes == null) {
|
|
||||||
log.e(
|
log.e(
|
||||||
"oAuth Server URL not available. Kindly ensure oAuth login is enabled in the server",
|
"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(
|
final oAuthCallbackUrl = await FlutterWebAuth2.authenticate(
|
||||||
url: oAuthUrlRes,
|
url: oAuthUrl,
|
||||||
callbackUrlScheme: oAuthCallbackSchema,
|
callbackUrlScheme: oAuthCallbackSchema,
|
||||||
);
|
);
|
||||||
|
|
||||||
final loginResponse = await oAuthApi.finishOAuth(
|
return await authApi.finishOAuth(oAuthCallbackUrl);
|
||||||
OAuthCallbackDto(url: oAuthCallbackUrl),
|
|
||||||
);
|
|
||||||
|
|
||||||
return loginResponse?.accessToken;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.e("Exception occured while performing oauth login", e);
|
log.e("Exception occured while performing oauth login", e);
|
||||||
}
|
}
|
||||||
@ -114,8 +106,7 @@ class LoginService with LogMixin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceLocator.registerApiClient(serverEndpoint);
|
await ServiceLocator.registerApiClient(serverEndpoint);
|
||||||
ServiceLocator.registerPostValidationServices();
|
|
||||||
ServiceLocator.registerPostGlobalStates();
|
ServiceLocator.registerPostGlobalStates();
|
||||||
|
|
||||||
final accessToken =
|
final accessToken =
|
||||||
@ -124,10 +115,10 @@ class LoginService with LogMixin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set token to interceptor
|
// Set token to interceptor
|
||||||
await di<ImApiClient>().init(accessToken: accessToken);
|
await di<ImApiClient>().init(accessToken: accessToken);
|
||||||
|
|
||||||
final user = await di<UserService>().getMyUser().timeout(
|
final user = await di<IUserApiRepository>().getMyUser().timeout(
|
||||||
const Duration(seconds: 10),
|
const Duration(seconds: 10),
|
||||||
// ignore: function-always-returns-null
|
// ignore: function-always-returns-null
|
||||||
onTimeout: () {
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
28
mobile-v2/lib/domain/utils/renderlist_providers.dart
Normal file
28
mobile-v2/lib/domain/utils/renderlist_providers.dart
Normal file
@ -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,
|
||||||
|
);
|
||||||
|
}
|
@ -1,11 +1,10 @@
|
|||||||
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:immich_mobile/i18n/strings.g.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/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/service_locator.dart';
|
||||||
import 'package:immich_mobile/utils/constants/globals.dart';
|
import 'package:immich_mobile/utils/constants/globals.dart';
|
||||||
|
|
||||||
@ -22,9 +21,9 @@ class _ImAppState extends State<ImApp> with WidgetsBindingObserver {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TranslationProvider(
|
return TranslationProvider(
|
||||||
child: BlocBuilder<AppThemeCubit, AppTheme>(
|
child: ValueListenableBuilder(
|
||||||
bloc: di(),
|
valueListenable: di<AppThemeProvider>(),
|
||||||
builder: (_, appTheme) => AppThemeBuilder(
|
builder: (_, appTheme, __) => _AppThemeBuilder(
|
||||||
theme: appTheme,
|
theme: appTheme,
|
||||||
builder: (ctx, lightTheme, darkTheme) => MaterialApp.router(
|
builder: (ctx, lightTheme, darkTheme) => MaterialApp.router(
|
||||||
debugShowCheckedModeBanner: false,
|
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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset.model.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/models/render_list.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/utils/renderlist_providers.dart';
|
||||||
import 'package:immich_mobile/utils/constants/globals.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> {
|
class AssetGridCubit extends Cubit<RenderList> {
|
||||||
final Stream<RenderList> _renderStream;
|
final RenderListProvider _renderListProvider;
|
||||||
final RenderListAssetProvider _assetProvider;
|
|
||||||
late final StreamSubscription _renderListSubscription;
|
late final StreamSubscription _renderListSubscription;
|
||||||
|
|
||||||
/// offset of the assets from last section in [_buf]
|
/// 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]
|
/// assets cache loaded from DB with offset [_bufOffset]
|
||||||
List<Asset> _buf = [];
|
List<Asset> _buf = [];
|
||||||
|
|
||||||
AssetGridCubit({
|
AssetGridCubit({required RenderListProvider renderListProvider})
|
||||||
required Stream<RenderList> renderStream,
|
: _renderListProvider = renderListProvider,
|
||||||
required RenderListAssetProvider assetProvider,
|
|
||||||
}) : _renderStream = renderStream,
|
|
||||||
_assetProvider = assetProvider,
|
|
||||||
super(RenderList.empty()) {
|
super(RenderList.empty()) {
|
||||||
_renderListSubscription = _renderStream.listen((renderList) {
|
_renderListSubscription =
|
||||||
|
_renderListProvider.renderStreamProvider().listen((renderList) {
|
||||||
_bufOffset = 0;
|
_bufOffset = 0;
|
||||||
_buf = [];
|
_buf = [];
|
||||||
emit(renderList);
|
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
|
// 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;
|
_bufOffset = start;
|
||||||
|
|
||||||
assert(_bufOffset <= offset);
|
assert(_bufOffset <= offset);
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
|
import 'package:immich_mobile/domain/utils/renderlist_providers.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/renderlist.interface.dart';
|
|
||||||
import 'package:immich_mobile/presentation/components/grid/immich_asset_grid.state.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/presentation/components/grid/immich_asset_grid.widget.dart';
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class HomePage extends StatelessWidget {
|
class HomePage extends StatelessWidget {
|
||||||
@ -16,8 +14,7 @@ class HomePage extends StatelessWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: BlocProvider(
|
body: BlocProvider(
|
||||||
create: (_) => AssetGridCubit(
|
create: (_) => AssetGridCubit(
|
||||||
renderStream: di<IRenderListRepository>().watchAll(),
|
renderListProvider: RenderListProvider.mainTimeline(),
|
||||||
assetProvider: di<IAssetRepository>().getAll,
|
|
||||||
),
|
),
|
||||||
child: const ImAssetGrid(),
|
child: const ImAssetGrid(),
|
||||||
),
|
),
|
||||||
|
@ -12,7 +12,7 @@ class LoginPageState {
|
|||||||
required this.isLoginSuccessful,
|
required this.isLoginSuccessful,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory LoginPageState.reset() {
|
factory LoginPageState.initial() {
|
||||||
return const LoginPageState(
|
return const LoginPageState(
|
||||||
isServerValidated: false,
|
isServerValidated: false,
|
||||||
isValidationInProgress: false,
|
isValidationInProgress: false,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
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/asset.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/user.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/album_sync.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/asset_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/login.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
|
||||||
import 'package:immich_mobile/i18n/strings.g.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/modules/login/models/login_page.model.dart';
|
||||||
import 'package:immich_mobile/presentation/states/gallery_permission.state.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';
|
import 'package:immich_mobile/utils/snackbar_manager.dart';
|
||||||
|
|
||||||
class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
|
class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
|
||||||
LoginPageCubit() : super(LoginPageState.reset());
|
LoginPageCubit() : super(LoginPageState.initial());
|
||||||
|
|
||||||
String _appendSchema(String url) {
|
String _appendSchema(String url) {
|
||||||
// Add schema if none is set
|
// Add schema if none is set
|
||||||
@ -68,8 +68,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
|
|||||||
url = await loginService.resolveEndpoint(uri);
|
url = await loginService.resolveEndpoint(uri);
|
||||||
|
|
||||||
di<IStoreRepository>().upsert(StoreKey.serverEndpoint, url);
|
di<IStoreRepository>().upsert(StoreKey.serverEndpoint, url);
|
||||||
ServiceLocator.registerApiClient(url);
|
await ServiceLocator.registerApiClient(url);
|
||||||
ServiceLocator.registerPostValidationServices();
|
|
||||||
ServiceLocator.registerPostGlobalStates();
|
ServiceLocator.registerPostGlobalStates();
|
||||||
|
|
||||||
// Fetch server features
|
// Fetch server features
|
||||||
@ -130,7 +129,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
|
|||||||
/// Set token to interceptor
|
/// Set token to interceptor
|
||||||
await di<ImApiClient>().init(accessToken: accessToken);
|
await di<ImApiClient>().init(accessToken: accessToken);
|
||||||
|
|
||||||
final user = await di<UserService>().getMyUser();
|
final user = await di<IUserApiRepository>().getMyUser();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
SnackbarManager.showError(t.login.error.error_login);
|
SnackbarManager.showError(t.login.error.error_login);
|
||||||
return;
|
return;
|
||||||
@ -152,6 +151,6 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resetServerValidation() {
|
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/modules/login/states/login_page.state.dart';
|
||||||
import 'package:immich_mobile/presentation/router/router.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/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/service_locator.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log.mixin.dart';
|
||||||
|
|
||||||
@ -52,12 +53,13 @@ class _SplashScreenState extends State<SplashScreenPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _tryLogin() async {
|
Future<void> _tryLogin() async {
|
||||||
|
await di<GalleryPermissionProvider>().requestPermission();
|
||||||
if (await di<LoginService>().tryAutoLogin() && mounted) {
|
if (await di<LoginService>().tryAutoLogin() && mounted) {
|
||||||
unawaited(di<AssetSyncService>()
|
unawaited(di<AssetSyncService>()
|
||||||
.performFullRemoteSyncIsolate(di<CurrentUserProvider>().value));
|
.performFullRemoteSyncIsolate(di<CurrentUserProvider>().value));
|
||||||
unawaited(di<AlbumSyncService>().performFullDeviceSyncIsolate());
|
unawaited(di<AlbumSyncService>().performFullDeviceSyncIsolate());
|
||||||
unawaited(context.replaceRoute(const TabControllerRoute()));
|
unawaited(context.replaceRoute(const TabControllerRoute()));
|
||||||
} else {
|
} else if (mounted) {
|
||||||
unawaited(context.replaceRoute(const LoginRoute()));
|
unawaited(context.replaceRoute(const LoginRoute()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
mobile-v2/lib/presentation/states/app_theme.state.dart
Normal file
25
mobile-v2/lib/presentation/states/app_theme.state.dart
Normal file
@ -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: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/models/server-info/server_feature_config.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/server_info.service.dart';
|
|
||||||
|
|
||||||
class ServerFeatureConfigProvider extends ValueNotifier<ServerFeatureConfig> {
|
class ServerFeatureConfigProvider extends ValueNotifier<ServerFeatureConfig> {
|
||||||
final ServerInfoService _serverInfoService;
|
final IServerApiRepository _serverApiRepository;
|
||||||
|
|
||||||
ServerFeatureConfigProvider(this._serverInfoService)
|
ServerFeatureConfigProvider({required IServerApiRepository serverApiRepo})
|
||||||
: super(const ServerFeatureConfig.reset());
|
: _serverApiRepository = serverApiRepo,
|
||||||
|
super(const ServerFeatureConfig.initial());
|
||||||
|
|
||||||
Future<void> getFeatures() async =>
|
Future<void> getFeatures() async =>
|
||||||
await Future.wait([_getFeatures(), _getConfig()]);
|
await Future.wait([_getFeatures(), _getConfig()]);
|
||||||
|
|
||||||
Future<void> _getFeatures() async {
|
Future<void> _getFeatures() async {
|
||||||
final features = await _serverInfoService.getServerFeatures();
|
final features = await _serverApiRepository.getServerFeatures();
|
||||||
if (features != null) {
|
if (features != null) {
|
||||||
value = value.copyWith(features: features);
|
value = value.copyWith(features: features);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _getConfig() async {
|
Future<void> _getConfig() async {
|
||||||
final config = await _serverInfoService.getServerConfig();
|
final config = await _serverApiRepository.getServerConfig();
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
value = value.copyWith(config: config);
|
value = value.copyWith(config: config);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
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';
|
import 'package:immich_mobile/utils/extensions/material_state.extension.dart';
|
||||||
|
|
||||||
enum AppTheme {
|
enum AppTheme {
|
@ -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.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/album_asset.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/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/asset.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/device_album.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.repository.dart';
|
||||||
import 'package:immich_mobile/domain/repositories/album_asset.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/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/asset.repository.dart';
|
||||||
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||||
import 'package:immich_mobile/domain/repositories/device_album.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/asset_sync.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/hash.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/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/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/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/current_user.state.dart';
|
||||||
import 'package:immich_mobile/presentation/states/gallery_permission.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';
|
import 'package:immich_mobile/presentation/states/server_info/server_feature_config.state.dart';
|
||||||
@ -68,6 +74,7 @@ class ServiceLocator {
|
|||||||
_registerSingleton(DriftDatabaseRepository());
|
_registerSingleton(DriftDatabaseRepository());
|
||||||
_registerRepositories();
|
_registerRepositories();
|
||||||
_registerPreGlobalStates();
|
_registerPreGlobalStates();
|
||||||
|
_registerServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void configureServicesForIsolate({
|
static void configureServicesForIsolate({
|
||||||
@ -78,37 +85,63 @@ class ServiceLocator {
|
|||||||
_registerSingleton(apiClient);
|
_registerSingleton(apiClient);
|
||||||
|
|
||||||
_registerRepositories();
|
_registerRepositories();
|
||||||
registerPostValidationServices();
|
_registerServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _registerRepositories() {
|
static void _registerRepositories() {
|
||||||
/// Repositories
|
// Used for transactions
|
||||||
_registerSingleton<IDatabaseRepository>(di<DriftDatabaseRepository>());
|
_registerSingleton<IDatabaseRepository>(di<DriftDatabaseRepository>());
|
||||||
_registerFactory<IStoreRepository>(() => StoreRepository(di()));
|
_registerSingleton(ImApiClient(endpoint: ''));
|
||||||
_registerFactory<ILogRepository>(() => LogRepository(di()));
|
|
||||||
_registerFactory<AppSettingService>(() => AppSettingService(di()));
|
_registerFactory<IStoreRepository>(() => StoreRepository(db: di()));
|
||||||
_registerFactory<IUserRepository>(() => UserRepository(di()));
|
_registerFactory<ILogRepository>(() => LogRepository(db: di()));
|
||||||
_registerFactory<IAssetRepository>(() => AssetRepository(di()));
|
_registerFactory<AppSettingService>(() => AppSettingService(store: di()));
|
||||||
_registerFactory<IAlbumRepository>(() => AlbumRepository(di()));
|
_registerFactory<IUserRepository>(() => UserRepository(db: di()));
|
||||||
|
_registerFactory<IAssetRepository>(() => AssetRepository(db: di()));
|
||||||
|
_registerFactory<IAlbumRepository>(() => AlbumRepository(db: di()));
|
||||||
_registerFactory<IDeviceAssetRepository>(
|
_registerFactory<IDeviceAssetRepository>(
|
||||||
() => const DeviceAssetRepository(),
|
() => const DeviceAssetRepository(),
|
||||||
);
|
);
|
||||||
_registerFactory<IRenderListRepository>(() => RenderListRepository(di()));
|
_registerFactory<IRenderListRepository>(
|
||||||
|
() => RenderListRepository(db: di()),
|
||||||
|
);
|
||||||
_registerFactory<IDeviceAssetToHashRepository>(
|
_registerFactory<IDeviceAssetToHashRepository>(
|
||||||
() => DeviceAssetToHashRepository(di()),
|
() => DeviceAssetToHashRepository(db: di()),
|
||||||
);
|
);
|
||||||
_registerFactory<IDeviceAlbumRepository>(
|
_registerFactory<IDeviceAlbumRepository>(
|
||||||
() => const DeviceAlbumRepository(),
|
() => const DeviceAlbumRepository(),
|
||||||
);
|
);
|
||||||
_registerFactory<IAlbumToAssetRepository>(
|
_registerFactory<IAlbumToAssetRepository>(
|
||||||
() => AlbumToAssetRepository(di()),
|
() => AlbumToAssetRepository(db: di()),
|
||||||
);
|
);
|
||||||
_registerFactory<IAlbumETagRepository>(() => AlbumETagRepository(di()));
|
|
||||||
|
|
||||||
/// Services
|
/// API Repos
|
||||||
_registerFactory<LoginService>(() => const LoginService());
|
_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(ImHostService());
|
||||||
_registerSingleton(const AlbumSyncService());
|
_registerSingleton(const AlbumSyncService());
|
||||||
|
_registerSingleton(const AssetSyncService());
|
||||||
|
|
||||||
|
///
|
||||||
|
_registerFactory<LoginService>(() => const LoginService());
|
||||||
_registerFactory<HashService>(() => HashService(
|
_registerFactory<HashService>(() => HashService(
|
||||||
hostService: di(),
|
hostService: di(),
|
||||||
assetToHashRepo: di(),
|
assetToHashRepo: di(),
|
||||||
@ -119,30 +152,23 @@ class ServiceLocator {
|
|||||||
|
|
||||||
static void _registerPreGlobalStates() {
|
static void _registerPreGlobalStates() {
|
||||||
_registerSingleton(AppRouter());
|
_registerSingleton(AppRouter());
|
||||||
_registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
|
_registerLazySingleton<AppThemeProvider>(
|
||||||
|
() => AppThemeProvider(settingsService: di()),
|
||||||
|
);
|
||||||
_registerSingleton(GalleryPermissionProvider());
|
_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() {
|
static void registerPostGlobalStates() {
|
||||||
_registerLazySingleton<ServerFeatureConfigProvider>(
|
_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) {
|
static void registerCurrentUser(User user) {
|
||||||
_registerSingleton(CurrentUserProvider(user));
|
_registerSingleton(CurrentUserProvider(user));
|
||||||
}
|
}
|
||||||
|
@ -114,10 +114,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04
|
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.12"
|
version: "2.4.13"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -270,14 +270,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
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:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
|
sha256: c4af09051b4f0508f6c1dc0a5c085bf014d5c9a4a0678ce1799c2b4d716387a0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.1.2"
|
version: "11.1.0"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -290,26 +298,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift
|
name: drift
|
||||||
sha256: "5b561ec76fff260e1e0593a29ca0d058a140a4b4dfb11dcc0c3813820cd20200"
|
sha256: df027d168a2985a2e9da900adeba2ab0136f0d84436592cf3cd5135f82c8579c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.20.2"
|
version: "2.21.0"
|
||||||
drift_dev:
|
drift_dev:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: drift_dev
|
name: drift_dev
|
||||||
sha256: "3ee987578ca2281b5ff91eadd757cd6dd36001458d6e33784f990d67ff38f756"
|
sha256: "27bab15e7869b69259663590381180117873b9b273a1ea9ebb21bb73133d1233"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.20.3"
|
version: "2.21.0"
|
||||||
drift_flutter:
|
drift_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift_flutter
|
name: drift_flutter
|
||||||
sha256: c670c947fe17ad149678a43fdbbfdb69321f0c83d315043e34e8ad2729e11f49
|
sha256: fec503e9d408f36bb345f9f6d24bc9d62b7b5f970db49760253d9e8d3acd48d5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.1"
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -383,26 +391,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_gen_core
|
name: flutter_gen_core
|
||||||
sha256: "638d518897f1aefc55a24278968027591d50223a6943b6ae9aa576fe1494d99d"
|
sha256: "46ecf0e317413dd065547887c43f93f55e9653e83eb98dc13dd07d40dd225325"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.7.0"
|
version: "5.8.0"
|
||||||
flutter_gen_runner:
|
flutter_gen_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_gen_runner
|
name: flutter_gen_runner
|
||||||
sha256: "7f2f02d95e3ec96cf70a1c515700c0dd3ea905af003303a55d6fb081240e6b8a"
|
sha256: "77f0a02fc30d9fcf2549fe874eb3fde091435724904bcbb1af60aa40cbfab1f4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.7.0"
|
version: "5.8.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "5.0.0"
|
||||||
flutter_list_view:
|
flutter_list_view:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -425,18 +433,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_web_auth_2
|
name: flutter_web_auth_2
|
||||||
sha256: "4d3d2fd3d26bf1a26b3beafd4b4b899c0ffe10dc99af25abc58ffe24e991133c"
|
sha256: "8f59c9fa71b5affb322cb7103b836cd0ced89c9c50c66f82b523b7d339018dc3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "4.0.1"
|
||||||
flutter_web_auth_2_platform_interface:
|
flutter_web_auth_2_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_web_auth_2_platform_interface
|
name: flutter_web_auth_2_platform_interface
|
||||||
sha256: e8669e262005a8354389ba2971f0fc1c36188481234ff50d013aaf993f30f739
|
sha256: "222264d4979e9372c90e441736a62d800481e4a9c860cc2c235d1d605a118a2b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "4.0.1"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -454,10 +462,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: get_it
|
name: get_it
|
||||||
sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1
|
sha256: ff97e5e7b2e82e63c82f5658c6ba2605ea831f0f7489b0d2fb255d817ec4eb5e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.7.0"
|
version: "8.0.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -582,18 +590,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "5.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: logging
|
name: logging
|
||||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.0"
|
||||||
macros:
|
macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -622,10 +630,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: material_symbols_icons
|
name: material_symbols_icons
|
||||||
sha256: "66416c4e30bd363508e12669634fc4f3250b83b69e862de67f4f9c480cf42414"
|
sha256: "7626ce90395bc6dc2ecb7bdd84c04a97f3f084a4e923ff73791c3c409af02804"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2785.1"
|
version: "4.2789.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -677,10 +685,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
|
sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.2"
|
version: "8.1.0"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -813,26 +821,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: photo_manager
|
name: photo_manager
|
||||||
sha256: e29619443803c40385ee509abc7937835d9b5122f899940080d28b2dceed59c1
|
sha256: "70159eee32203e8162d49d588232f0299ed3f383c63eef1e899cb6b83dee6b26"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "3.5.1"
|
||||||
photo_manager_image_provider:
|
photo_manager_image_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: photo_manager_image_provider
|
name: photo_manager_image_provider
|
||||||
sha256: "38ef1023dc11de3a8669f16e7c981673b3c5cfee715d17120f4b87daa2cdd0af"
|
sha256: b6015b67b32f345f57cf32c126f871bced2501236c405aafaefa885f7c821e4f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.2.0"
|
||||||
pigeon:
|
pigeon:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: pigeon
|
name: pigeon
|
||||||
sha256: "95481446c02fa79fcf0e8014882f8a3b87fd06c257e9e1c3d4cc6d102a925ad8"
|
sha256: "2f7af49f530b3208131489ce601f8d95d567b3fa3c71265a87f62b0b87d41e91"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "22.4.0"
|
version: "22.5.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -922,26 +930,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: slang
|
name: slang
|
||||||
sha256: a2f704508bf9f209b71c881347bd27de45309651e9bd63570e4dd6ed2a77fbd2
|
sha256: a466773de768eb95bdf681e0a92e7c8010d44bb247b62130426c83ece33aeaed
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.31.2"
|
version: "3.32.0"
|
||||||
slang_build_runner:
|
slang_build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: slang_build_runner
|
name: slang_build_runner
|
||||||
sha256: "6e60160e8000b91824c47221b20d9642e7408287a5a21837ecefc75270197586"
|
sha256: b2e0c63f3c801a4aa70b4ca43173893d6eb7d5a421fc9d97ad983527397631b3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.31.0"
|
version: "3.32.0"
|
||||||
slang_flutter:
|
slang_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: slang_flutter
|
name: slang_flutter
|
||||||
sha256: f8400292be49c11697d94af58d7f7d054c91af759f41ffe71e4e5413871ffc62
|
sha256: "1a98e878673996902fa5ef0b61ce5c245e41e4d25640d18af061c6aab917b0c7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.31.0"
|
version: "3.32.0"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1002,10 +1010,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlparser
|
name: sqlparser
|
||||||
sha256: "852cf80f9e974ac8e1b613758a8aa640215f7701352b66a7f468e95711eb570b"
|
sha256: c5f63dff8677407ddcddfa4744c176ea6dc44286c47ba9e69e76d8071398034d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.38.1"
|
version: "0.39.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1090,10 +1098,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
|
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.1"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
name: immich_mobile
|
name: immich_mobile
|
||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: "none"
|
publish_to: 'none'
|
||||||
version: 1.102.0+132
|
version: 1.102.0+132
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.3 <4.0.0"
|
sdk: '>=3.3.3 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@ -17,15 +17,15 @@ dependencies:
|
|||||||
path_provider: ^2.1.4
|
path_provider: ^2.1.4
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
url_launcher: ^6.3.0
|
url_launcher: ^6.3.1
|
||||||
package_info_plus: ^8.0.2
|
package_info_plus: ^8.1.0
|
||||||
device_info_plus: ^10.1.2
|
device_info_plus: ^11.1.0
|
||||||
permission_handler: ^11.3.1
|
permission_handler: ^11.3.1
|
||||||
# State handling
|
# State handling
|
||||||
flutter_bloc: ^8.1.6
|
flutter_bloc: ^8.1.6
|
||||||
# Database
|
# Database
|
||||||
drift: ^2.20.2
|
drift: ^2.21.0
|
||||||
drift_flutter: ^0.2.0
|
drift_flutter: ^0.2.1
|
||||||
sqlite3: ^2.4.6
|
sqlite3: ^2.4.6
|
||||||
sqlite3_flutter_libs: ^0.5.24
|
sqlite3_flutter_libs: ^0.5.24
|
||||||
# Network
|
# Network
|
||||||
@ -33,23 +33,23 @@ dependencies:
|
|||||||
# Route handling
|
# Route handling
|
||||||
auto_route: ^9.2.2
|
auto_route: ^9.2.2
|
||||||
# Logging
|
# Logging
|
||||||
logging: ^1.2.0
|
logging: ^1.3.0
|
||||||
# Collection Utils
|
# Collection Utils
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
# service_locator
|
# service_locator
|
||||||
get_it: ^7.7.0
|
get_it: ^8.0.0
|
||||||
# Photo Manager
|
# Photo Manager
|
||||||
photo_manager: ^3.3.0
|
photo_manager: ^3.5.1
|
||||||
photo_manager_image_provider: ^2.1.1
|
photo_manager_image_provider: ^2.2.0
|
||||||
# Localization
|
# Localization
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
slang: ^3.31.2
|
slang: ^3.32.0
|
||||||
slang_flutter: ^3.31.0
|
slang_flutter: ^3.32.0
|
||||||
# oauth login
|
# oauth login
|
||||||
flutter_web_auth_2: ^3.1.2
|
flutter_web_auth_2: ^4.0.1
|
||||||
# components
|
# components
|
||||||
octo_image: ^2.1.0
|
octo_image: ^2.1.0
|
||||||
material_symbols_icons: ^4.2785.1
|
material_symbols_icons: ^4.2789.0
|
||||||
flutter_adaptive_scaffold: ^0.3.1
|
flutter_adaptive_scaffold: ^0.3.1
|
||||||
flutter_list_view: ^1.1.28
|
flutter_list_view: ^1.1.28
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.4.1
|
||||||
@ -63,19 +63,19 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# Recommended lints
|
# Recommended lints
|
||||||
flutter_lints: ^4.0.0
|
flutter_lints: ^5.0.0
|
||||||
# Code generator
|
# Code generator
|
||||||
build_runner: ^2.4.12
|
build_runner: ^2.4.13
|
||||||
# Database helper
|
# Database helper
|
||||||
drift_dev: ^2.20.3
|
drift_dev: ^2.21.0
|
||||||
# Route helper
|
# Route helper
|
||||||
auto_route_generator: ^9.0.0
|
auto_route_generator: ^9.0.0
|
||||||
# Localization generator
|
# Localization generator
|
||||||
slang_build_runner: ^3.31.0
|
slang_build_runner: ^3.32.0
|
||||||
# Assets constant generator
|
# Assets constant generator
|
||||||
flutter_gen_runner: ^5.7.0
|
flutter_gen_runner: ^5.8.0
|
||||||
# Type safe platform channels
|
# Type safe platform channels
|
||||||
pigeon: ^22.4.0
|
pigeon: ^22.5.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user