mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
add proper logging
This commit is contained in:
parent
1631df70e9
commit
75448ce56b
@ -4,7 +4,8 @@ include: package:flutter_lints/flutter.yaml
|
|||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
avoid_single_cascade_in_expression_statements: false
|
- avoid_single_cascade_in_expression_statements: false
|
||||||
|
- unawaited_futures
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
exclude:
|
exclude:
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"empty_server_url": "Kindly provide a server URL",
|
"empty_server_url": "Kindly provide a server URL",
|
||||||
"invalid_server_url": "Invalid URL",
|
"invalid_server_url": "Invalid URL",
|
||||||
"server_not_reachable": "Server is not reachable"
|
"server_not_reachable": "Server is not reachable",
|
||||||
|
"error_login": "Error logging in",
|
||||||
|
"error_login_oauth": "Error logging using OAuth, check server URL"
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
|
@ -4,7 +4,7 @@ class LocalAlbum extends Table {
|
|||||||
const LocalAlbum();
|
const LocalAlbum();
|
||||||
|
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
TextColumn get localId => text()();
|
TextColumn get localId => text().unique()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
DateTimeColumn get modifiedTime =>
|
DateTimeColumn get modifiedTime =>
|
||||||
dateTime().withDefault(currentDateAndTime)();
|
dateTime().withDefault(currentDateAndTime)();
|
||||||
|
@ -5,9 +5,9 @@ class LocalAsset extends Table {
|
|||||||
const LocalAsset();
|
const LocalAsset();
|
||||||
|
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer().autoIncrement()();
|
||||||
TextColumn get localId => text()();
|
TextColumn get localId => text().unique()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
TextColumn get checksum => text()();
|
TextColumn get checksum => text().unique()();
|
||||||
IntColumn get height => integer()();
|
IntColumn get height => integer()();
|
||||||
IntColumn get width => integer()();
|
IntColumn get width => integer()();
|
||||||
IntColumn get type => intEnum<AssetType>()();
|
IntColumn get type => intEnum<AssetType>()();
|
||||||
|
@ -6,7 +6,10 @@ class Store extends Table {
|
|||||||
@override
|
@override
|
||||||
String get tableName => 'store';
|
String get tableName => 'store';
|
||||||
|
|
||||||
IntColumn get id => integer().autoIncrement()();
|
IntColumn get id => integer()();
|
||||||
IntColumn get intValue => integer().nullable()();
|
IntColumn get intValue => integer().nullable()();
|
||||||
TextColumn get stringValue => text().nullable()();
|
TextColumn get stringValue => text().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
|
24
mobile-v2/lib/domain/entities/user.entity.dart
Normal file
24
mobile-v2/lib/domain/entities/user.entity.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
|
class User extends Table {
|
||||||
|
const User();
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
TextColumn get email => text()();
|
||||||
|
BoolColumn get isAdmin => boolean().withDefault(const Constant(false))();
|
||||||
|
// Quota
|
||||||
|
IntColumn get quotaSizeInBytes => integer().withDefault(const Constant(0))();
|
||||||
|
IntColumn get quotaUsageInBytes => integer().withDefault(const Constant(0))();
|
||||||
|
// Sharing
|
||||||
|
BoolColumn get inTimeline => boolean().withDefault(const Constant(false))();
|
||||||
|
// User prefs
|
||||||
|
TextColumn get profileImagePath => text()();
|
||||||
|
BoolColumn get memoryEnabled => boolean().withDefault(const Constant(true))();
|
||||||
|
IntColumn get avatarColor => intEnum<UserAvatarColor>()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
@ -6,6 +6,15 @@ abstract class ILogRepository {
|
|||||||
/// Fetches all logs
|
/// Fetches all logs
|
||||||
FutureOr<List<LogMessage>> fetchLogs();
|
FutureOr<List<LogMessage>> fetchLogs();
|
||||||
|
|
||||||
|
/// Inserts a new log into the DB
|
||||||
|
FutureOr<bool> add(LogMessage log);
|
||||||
|
|
||||||
|
/// Bulk insert logs into DB
|
||||||
|
FutureOr<bool> addAll(List<LogMessage> log);
|
||||||
|
|
||||||
|
/// Clears all logs
|
||||||
|
FutureOr<bool> clear();
|
||||||
|
|
||||||
/// Truncates the logs to the most recent [limit]. Defaults to recent 250 logs
|
/// Truncates the logs to the most recent [limit]. Defaults to recent 250 logs
|
||||||
FutureOr<void> truncateLogs({int limit = 250});
|
FutureOr<void> truncateLogs({int limit = 250});
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,19 @@ abstract class IStoreConverter<T, U> {
|
|||||||
U toPrimitive(T value);
|
U toPrimitive(T value);
|
||||||
|
|
||||||
/// Converts the value back to T? from the primitive type U from the Store
|
/// Converts the value back to T? from the primitive type U from the Store
|
||||||
T? fromPrimitive(U value);
|
FutureOr<T?> fromPrimitive(U value);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class IStoreRepository {
|
abstract class IStoreRepository {
|
||||||
FutureOr<T?> getValue<T, U>(StoreKey<T, U> key);
|
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key);
|
||||||
|
|
||||||
FutureOr<bool> setValue<T, U>(StoreKey<T, U> key, T value);
|
FutureOr<T> get<T, U>(StoreKey<T, U> key);
|
||||||
|
|
||||||
FutureOr<void> deleteValue(StoreKey key);
|
FutureOr<bool> set<T, U>(StoreKey<T, U> key, T value);
|
||||||
|
|
||||||
Stream<T?> watchValue<T, U>(StoreKey<T, U> key);
|
FutureOr<void> delete(StoreKey key);
|
||||||
|
|
||||||
Stream<List<StoreValue>> watchStore();
|
Stream<T?> watch<T, U>(StoreKey<T, U> key);
|
||||||
|
|
||||||
FutureOr<void> clearStore();
|
FutureOr<void> clearStore();
|
||||||
}
|
}
|
||||||
|
11
mobile-v2/lib/domain/interfaces/user.interface.dart
Normal file
11
mobile-v2/lib/domain/interfaces/user.interface.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
|
abstract class IUserRepository {
|
||||||
|
/// Fetches user
|
||||||
|
FutureOr<User?> getUser(String userId);
|
||||||
|
|
||||||
|
/// Insert user
|
||||||
|
FutureOr<bool> insertUser(User user);
|
||||||
|
}
|
@ -24,7 +24,6 @@ extension LevelExtension on Level {
|
|||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class LogMessage {
|
class LogMessage {
|
||||||
final int id;
|
|
||||||
final String content;
|
final String content;
|
||||||
final LogLevel level;
|
final LogLevel level;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
@ -33,7 +32,6 @@ class LogMessage {
|
|||||||
final String? stack;
|
final String? stack;
|
||||||
|
|
||||||
const LogMessage({
|
const LogMessage({
|
||||||
required this.id,
|
|
||||||
required this.content,
|
required this.content,
|
||||||
required this.level,
|
required this.level,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
@ -51,8 +49,7 @@ class LogMessage {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return id.hashCode ^
|
return content.hashCode ^
|
||||||
content.hashCode ^
|
|
||||||
level.hashCode ^
|
level.hashCode ^
|
||||||
createdAt.hashCode ^
|
createdAt.hashCode ^
|
||||||
logger.hashCode ^
|
logger.hashCode ^
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/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/modules/theme/models/app_theme.model.dart';
|
||||||
|
|
||||||
@ -21,14 +22,33 @@ class StoreValue<T> {
|
|||||||
int get hashCode => id.hashCode ^ value.hashCode;
|
int get hashCode => id.hashCode ^ value.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StoreKeyNotFoundException implements Exception {
|
||||||
|
final StoreKey key;
|
||||||
|
const StoreKeyNotFoundException(this.key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "Key '${key.name}' not found in Store";
|
||||||
|
}
|
||||||
|
|
||||||
/// Key for each possible value in the `Store`.
|
/// Key for each possible value in the `Store`.
|
||||||
/// Also stores the converter to convert the value to and from the store and the type of value stored in the Store
|
/// Also stores the converter to convert the value to and from the store and the type of value stored in the Store
|
||||||
enum StoreKey<T, U> {
|
enum StoreKey<T, U> {
|
||||||
serverEndpoint<String, String>(
|
serverEndpoint<String, String>(
|
||||||
0,
|
0,
|
||||||
converter: StorePrimitiveConverter(),
|
converter: StoreStringConverter(),
|
||||||
type: String,
|
type: String,
|
||||||
),
|
),
|
||||||
|
accessToken<String, String>(
|
||||||
|
1,
|
||||||
|
converter: StoreStringConverter(),
|
||||||
|
type: String,
|
||||||
|
),
|
||||||
|
currentUser<User, String>(
|
||||||
|
2,
|
||||||
|
converter: StoreUserConverter(),
|
||||||
|
type: String,
|
||||||
|
),
|
||||||
|
// App settings
|
||||||
appTheme<AppTheme, int>(
|
appTheme<AppTheme, int>(
|
||||||
1000,
|
1000,
|
||||||
converter: StoreEnumConverter(AppTheme.values),
|
converter: StoreEnumConverter(AppTheme.values),
|
||||||
@ -44,7 +64,7 @@ enum StoreKey<T, U> {
|
|||||||
const StoreKey(this.id, {required this.converter, required this.type});
|
const StoreKey(this.id, {required this.converter, required this.type});
|
||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
/// Type is also stored here easily fetch it during runtime
|
/// Primitive Type is also stored here to easily fetch it during runtime
|
||||||
final Type type;
|
final Type type;
|
||||||
final IStoreConverter<T, U> converter;
|
final IStoreConverter<T, U> converter;
|
||||||
}
|
}
|
||||||
|
187
mobile-v2/lib/domain/models/user.model.dart
Normal file
187
mobile-v2/lib/domain/models/user.model.dart
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:openapi/openapi.dart' as api;
|
||||||
|
|
||||||
|
class User {
|
||||||
|
const User({
|
||||||
|
required this.id,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.name,
|
||||||
|
required this.email,
|
||||||
|
required this.isAdmin,
|
||||||
|
required this.quotaSizeInBytes,
|
||||||
|
required this.quotaUsageInBytes,
|
||||||
|
required this.inTimeline,
|
||||||
|
required this.profileImagePath,
|
||||||
|
required this.memoryEnabled,
|
||||||
|
required this.avatarColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final String name;
|
||||||
|
final String email;
|
||||||
|
final bool isAdmin;
|
||||||
|
// Quota
|
||||||
|
final int quotaSizeInBytes;
|
||||||
|
final int quotaUsageInBytes;
|
||||||
|
// Sharing
|
||||||
|
final bool inTimeline;
|
||||||
|
// User prefs
|
||||||
|
final String profileImagePath;
|
||||||
|
final bool memoryEnabled;
|
||||||
|
final UserAvatarColor avatarColor;
|
||||||
|
|
||||||
|
User copyWith({
|
||||||
|
String? id,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
String? name,
|
||||||
|
String? email,
|
||||||
|
bool? isAdmin,
|
||||||
|
int? quotaSizeInBytes,
|
||||||
|
int? quotaUsageInBytes,
|
||||||
|
bool? inTimeline,
|
||||||
|
String? profileImagePath,
|
||||||
|
bool? memoryEnabled,
|
||||||
|
UserAvatarColor? avatarColor,
|
||||||
|
}) {
|
||||||
|
return User(
|
||||||
|
id: id ?? this.id,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
name: name ?? this.name,
|
||||||
|
email: email ?? this.email,
|
||||||
|
isAdmin: isAdmin ?? this.isAdmin,
|
||||||
|
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
|
||||||
|
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
|
||||||
|
inTimeline: inTimeline ?? this.inTimeline,
|
||||||
|
profileImagePath: profileImagePath ?? this.profileImagePath,
|
||||||
|
memoryEnabled: memoryEnabled ?? this.memoryEnabled,
|
||||||
|
avatarColor: avatarColor ?? this.avatarColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'User(id: $id, updatedAt: $updatedAt, name: $name, email: $email, isAdmin: $isAdmin, quotaSizeInBytes: $quotaSizeInBytes, quotaUsageInBytes: $quotaUsageInBytes, inTimeline: $inTimeline, profileImagePath: $profileImagePath, memoryEnabled: $memoryEnabled, avatarColor: $avatarColor)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant User other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.id == id &&
|
||||||
|
other.updatedAt == updatedAt &&
|
||||||
|
other.name == name &&
|
||||||
|
other.email == email &&
|
||||||
|
other.isAdmin == isAdmin &&
|
||||||
|
other.quotaSizeInBytes == quotaSizeInBytes &&
|
||||||
|
other.quotaUsageInBytes == quotaUsageInBytes &&
|
||||||
|
other.inTimeline == inTimeline &&
|
||||||
|
other.profileImagePath == profileImagePath &&
|
||||||
|
other.memoryEnabled == memoryEnabled &&
|
||||||
|
other.avatarColor == avatarColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return id.hashCode ^
|
||||||
|
updatedAt.hashCode ^
|
||||||
|
name.hashCode ^
|
||||||
|
email.hashCode ^
|
||||||
|
isAdmin.hashCode ^
|
||||||
|
quotaSizeInBytes.hashCode ^
|
||||||
|
quotaUsageInBytes.hashCode ^
|
||||||
|
inTimeline.hashCode ^
|
||||||
|
profileImagePath.hashCode ^
|
||||||
|
memoryEnabled.hashCode ^
|
||||||
|
avatarColor.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory User.fromAdminDto(
|
||||||
|
api.UserAdminResponseDto userDto, [
|
||||||
|
api.UserPreferencesResponseDto? userPreferences,
|
||||||
|
]) {
|
||||||
|
return User(
|
||||||
|
id: userDto.id,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
name: userDto.name,
|
||||||
|
email: userDto.email,
|
||||||
|
isAdmin: userDto.isAdmin,
|
||||||
|
quotaSizeInBytes: userDto.quotaSizeInBytes ?? 0,
|
||||||
|
quotaUsageInBytes: userDto.quotaUsageInBytes ?? 0,
|
||||||
|
inTimeline: true,
|
||||||
|
profileImagePath: userDto.profileImagePath,
|
||||||
|
memoryEnabled: userPreferences?.memories.enabled ?? true,
|
||||||
|
avatarColor: userDto.avatarColor.toEnum(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserAvatarColor {
|
||||||
|
// do not change this order or reuse indices for other purposes, adding is OK
|
||||||
|
primary,
|
||||||
|
pink,
|
||||||
|
red,
|
||||||
|
yellow,
|
||||||
|
blue,
|
||||||
|
green,
|
||||||
|
purple,
|
||||||
|
orange,
|
||||||
|
gray,
|
||||||
|
amber,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AvatarColorEnumHelper on api.UserAvatarColor {
|
||||||
|
UserAvatarColor toEnum() {
|
||||||
|
switch (this) {
|
||||||
|
case api.UserAvatarColor.primary:
|
||||||
|
return UserAvatarColor.primary;
|
||||||
|
case api.UserAvatarColor.pink:
|
||||||
|
return UserAvatarColor.pink;
|
||||||
|
case api.UserAvatarColor.red:
|
||||||
|
return UserAvatarColor.red;
|
||||||
|
case api.UserAvatarColor.yellow:
|
||||||
|
return UserAvatarColor.yellow;
|
||||||
|
case api.UserAvatarColor.blue:
|
||||||
|
return UserAvatarColor.blue;
|
||||||
|
case api.UserAvatarColor.green:
|
||||||
|
return UserAvatarColor.green;
|
||||||
|
case api.UserAvatarColor.purple:
|
||||||
|
return UserAvatarColor.purple;
|
||||||
|
case api.UserAvatarColor.orange:
|
||||||
|
return UserAvatarColor.orange;
|
||||||
|
case api.UserAvatarColor.gray:
|
||||||
|
return UserAvatarColor.gray;
|
||||||
|
case api.UserAvatarColor.amber:
|
||||||
|
return UserAvatarColor.amber;
|
||||||
|
}
|
||||||
|
return UserAvatarColor.primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AvatarColorToColorHelper on UserAvatarColor {
|
||||||
|
Color toColor([bool isDarkTheme = false]) {
|
||||||
|
switch (this) {
|
||||||
|
case UserAvatarColor.primary:
|
||||||
|
return isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF);
|
||||||
|
case UserAvatarColor.pink:
|
||||||
|
return const Color.fromARGB(255, 244, 114, 182);
|
||||||
|
case UserAvatarColor.red:
|
||||||
|
return const Color.fromARGB(255, 239, 68, 68);
|
||||||
|
case UserAvatarColor.yellow:
|
||||||
|
return const Color.fromARGB(255, 234, 179, 8);
|
||||||
|
case UserAvatarColor.blue:
|
||||||
|
return const Color.fromARGB(255, 59, 130, 246);
|
||||||
|
case UserAvatarColor.green:
|
||||||
|
return const Color.fromARGB(255, 22, 163, 74);
|
||||||
|
case UserAvatarColor.purple:
|
||||||
|
return const Color.fromARGB(255, 147, 51, 234);
|
||||||
|
case UserAvatarColor.orange:
|
||||||
|
return const Color.fromARGB(255, 234, 88, 12);
|
||||||
|
case UserAvatarColor.gray:
|
||||||
|
return const Color.fromARGB(255, 75, 85, 99);
|
||||||
|
case UserAvatarColor.amber:
|
||||||
|
return const Color.fromARGB(255, 217, 119, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,14 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
|
// ignore: depend_on_referenced_packages
|
||||||
|
import 'package:drift_dev/api/migrations.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/domain/entities/album.entity.dart';
|
import 'package:immich_mobile/domain/entities/album.entity.dart';
|
||||||
import 'package:immich_mobile/domain/entities/asset.entity.dart';
|
import 'package:immich_mobile/domain/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/domain/entities/log.entity.dart';
|
import 'package:immich_mobile/domain/entities/log.entity.dart';
|
||||||
import 'package:immich_mobile/domain/entities/store.entity.dart';
|
import 'package:immich_mobile/domain/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/domain/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/database.interface.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@ -14,7 +18,7 @@ import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
|||||||
|
|
||||||
import 'database.repository.drift.dart';
|
import 'database.repository.drift.dart';
|
||||||
|
|
||||||
@DriftDatabase(tables: [Logs, Store, LocalAlbum, LocalAsset])
|
@DriftDatabase(tables: [Logs, Store, LocalAlbum, LocalAsset, User])
|
||||||
class DriftDatabaseRepository extends $DriftDatabaseRepository
|
class DriftDatabaseRepository extends $DriftDatabaseRepository
|
||||||
implements IDatabaseRepository<GeneratedDatabase> {
|
implements IDatabaseRepository<GeneratedDatabase> {
|
||||||
DriftDatabaseRepository() : super(_openConnection());
|
DriftDatabaseRepository() : super(_openConnection());
|
||||||
@ -51,6 +55,18 @@ class DriftDatabaseRepository extends $DriftDatabaseRepository
|
|||||||
@override
|
@override
|
||||||
// ignore: no-empty-block
|
// ignore: no-empty-block
|
||||||
void migrateDB() {
|
void migrateDB() {
|
||||||
// No migrations yet
|
// Migrations are handled automatically using the migrator field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
|
onCreate: (m) => m.createAll(),
|
||||||
|
beforeOpen: (details) async {
|
||||||
|
if (kDebugMode) {
|
||||||
|
await validateDatabaseSchema();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// ignore: no-empty-block
|
||||||
|
onUpgrade: (m, from, to) async {},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/domain/entities/log.entity.drift.dart';
|
import 'package:immich_mobile/domain/entities/log.entity.drift.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
@ -10,7 +14,7 @@ class LogDriftRepository implements ILogRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<LogMessage>> fetchLogs() async {
|
Future<List<LogMessage>> fetchLogs() async {
|
||||||
return await db.select(db.logs).map((l) => l.toModel()).get();
|
return await db.managers.logs.map((l) => l.toModel()).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -26,12 +30,65 @@ class LogDriftRepository implements ILogRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<bool> add(LogMessage log) async {
|
||||||
|
try {
|
||||||
|
await db.transaction(() async {
|
||||||
|
await db.into(db.logs).insert(LogsCompanion.insert(
|
||||||
|
content: log.content,
|
||||||
|
level: log.level,
|
||||||
|
createdAt: Value(log.createdAt),
|
||||||
|
error: Value(log.error),
|
||||||
|
logger: Value(log.logger),
|
||||||
|
stack: Value(log.stack),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error while adding a log to the DB - $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<bool> addAll(List<LogMessage> logs) async {
|
||||||
|
try {
|
||||||
|
await db.batch((b) {
|
||||||
|
b.insertAll(
|
||||||
|
db.logs,
|
||||||
|
logs.map((log) => LogsCompanion.insert(
|
||||||
|
content: log.content,
|
||||||
|
level: log.level,
|
||||||
|
createdAt: Value(log.createdAt),
|
||||||
|
error: Value(log.error),
|
||||||
|
logger: Value(log.logger),
|
||||||
|
stack: Value(log.stack),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error while adding a log to the DB - $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<bool> clear() async {
|
||||||
|
try {
|
||||||
|
await db.managers.logs.delete();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error while clearning the logs in DB - $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension _LogToLogMessage on Log {
|
extension _LogToLogMessage on Log {
|
||||||
LogMessage toModel() {
|
LogMessage toModel() {
|
||||||
return LogMessage(
|
return LogMessage(
|
||||||
id: id,
|
|
||||||
content: content,
|
content: content,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
level: level,
|
level: level,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/entities/store.entity.drift.dart';
|
import 'package:immich_mobile/domain/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
@ -14,7 +13,7 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
const StoreDriftRepository(this.db);
|
const StoreDriftRepository(this.db);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<T?> getValue<T, U>(StoreKey<T, U> key) async {
|
FutureOr<T?> tryGet<T, U>(StoreKey<T, U> key) async {
|
||||||
final storeData = await db.managers.store
|
final storeData = await db.managers.store
|
||||||
.filter((s) => s.id.equals(key.id))
|
.filter((s) => s.id.equals(key.id))
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
@ -22,7 +21,16 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<bool> setValue<T, U>(StoreKey<T, U> key, T value) async {
|
FutureOr<T> get<T, U>(StoreKey<T, U> key) async {
|
||||||
|
final value = await tryGet(key);
|
||||||
|
if (value == null) {
|
||||||
|
throw StoreKeyNotFoundException(key);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<bool> set<T, U>(StoreKey<T, U> key, T value) async {
|
||||||
try {
|
try {
|
||||||
await db.transaction(() async {
|
await db.transaction(() async {
|
||||||
final storeValue = key.converter.toPrimitive(value);
|
final storeValue = key.converter.toPrimitive(value);
|
||||||
@ -42,30 +50,18 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> deleteValue(StoreKey key) async {
|
FutureOr<void> delete(StoreKey key) async {
|
||||||
return await db.transaction(() async {
|
return await db.transaction(() async {
|
||||||
await db.managers.store.filter((s) => s.id.equals(key.id)).delete();
|
await db.managers.store.filter((s) => s.id.equals(key.id)).delete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<StoreValue>> watchStore() {
|
Stream<T?> watch<T, U>(StoreKey<T, U> key) {
|
||||||
return (db.select(db.store).map((s) {
|
|
||||||
final key = StoreKey.values.firstWhereOrNull((e) => e.id == s.id);
|
|
||||||
if (key != null) {
|
|
||||||
final value = _getValueFromStoreData(key, s);
|
|
||||||
return StoreValue(id: s.id, value: value);
|
|
||||||
}
|
|
||||||
return StoreValue(id: s.id, value: null);
|
|
||||||
})).watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Stream<T?> watchValue<T, U>(StoreKey<T, U> key) {
|
|
||||||
return db.managers.store
|
return db.managers.store
|
||||||
.filter((s) => s.id.equals(key.id))
|
.filter((s) => s.id.equals(key.id))
|
||||||
.watchSingleOrNull()
|
.watchSingleOrNull()
|
||||||
.map((e) => _getValueFromStoreData(key, e));
|
.asyncMap((e) async => await _getValueFromStoreData(key, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -75,7 +71,10 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
T? _getValueFromStoreData<T, U>(StoreKey<T, U> key, StoreData? data) {
|
FutureOr<T?> _getValueFromStoreData<T, U>(
|
||||||
|
StoreKey<T, U> key,
|
||||||
|
StoreData? data,
|
||||||
|
) async {
|
||||||
final primitive = switch (key.type) {
|
final primitive = switch (key.type) {
|
||||||
const (int) => data?.intValue,
|
const (int) => data?.intValue,
|
||||||
const (String) => data?.stringValue,
|
const (String) => data?.stringValue,
|
||||||
|
65
mobile-v2/lib/domain/repositories/user.repository.dart
Normal file
65
mobile-v2/lib/domain/repositories/user.repository.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/entities/user.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/repositories/database.repository.dart';
|
||||||
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
|
|
||||||
|
class UserDriftRepository with LogContext implements IUserRepository {
|
||||||
|
final DriftDatabaseRepository db;
|
||||||
|
|
||||||
|
const UserDriftRepository(this.db);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<User?> getUser(String userId) async {
|
||||||
|
return await db.managers.user
|
||||||
|
.filter((f) => f.id.equals(userId))
|
||||||
|
.map((u) => u.toModel())
|
||||||
|
.getSingleOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<bool> insertUser(User user) async {
|
||||||
|
try {
|
||||||
|
return await db.transaction(() async {
|
||||||
|
await db.into(db.user).insertOnConflictUpdate(UserCompanion.insert(
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
profileImagePath: user.profileImagePath,
|
||||||
|
avatarColor: user.avatarColor,
|
||||||
|
inTimeline: Value(user.inTimeline),
|
||||||
|
isAdmin: Value(user.isAdmin),
|
||||||
|
memoryEnabled: Value(user.memoryEnabled),
|
||||||
|
quotaSizeInBytes: Value(user.quotaSizeInBytes),
|
||||||
|
quotaUsageInBytes: Value(user.quotaSizeInBytes),
|
||||||
|
updatedAt: Value(user.updatedAt),
|
||||||
|
));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} catch (e, s) {
|
||||||
|
log.severe("Cannot insert User into table - $user", e, s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _UserDataToUser on UserData {
|
||||||
|
User toModel() {
|
||||||
|
return User(
|
||||||
|
id: id,
|
||||||
|
email: email,
|
||||||
|
avatarColor: avatarColor,
|
||||||
|
inTimeline: inTimeline,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
memoryEnabled: memoryEnabled,
|
||||||
|
name: name,
|
||||||
|
profileImagePath: profileImagePath,
|
||||||
|
quotaSizeInBytes: quotaSizeInBytes,
|
||||||
|
quotaUsageInBytes: quotaUsageInBytes,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,18 @@
|
|||||||
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
import 'package:immich_mobile/domain/models/app_setting.model.dart';
|
||||||
import 'package:immich_mobile/domain/store_manager.dart';
|
|
||||||
|
|
||||||
class AppSettingService {
|
class AppSettingService {
|
||||||
final StoreManager store;
|
final IStoreRepository store;
|
||||||
|
|
||||||
const AppSettingService(this.store);
|
const AppSettingService(this.store);
|
||||||
|
|
||||||
T getSetting<T>(AppSetting<T> setting) {
|
Future<T> getSetting<T>(AppSetting<T> setting) async {
|
||||||
return store.get(setting.storeKey, setting.defaultValue);
|
final value = await store.tryGet(setting.storeKey);
|
||||||
|
return value ?? setting.defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setSetting<T>(AppSetting<T> setting, T value) async {
|
Future<bool> setSetting<T>(AppSetting<T> setting, T value) async {
|
||||||
return await store.put(setting.storeKey, value);
|
return await store.set(setting.storeKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<T> watchSetting<T>(AppSetting<T> setting) {
|
Stream<T> watchSetting<T>(AppSetting<T> setting) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||||
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:openapi/openapi.dart';
|
import 'package:openapi/openapi.dart';
|
||||||
|
|
||||||
@ -52,4 +54,58 @@ class LoginService with LogContext {
|
|||||||
// No well-known, return the baseUrl
|
// No well-known, return the baseUrl
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> passwordLogin(String email, String password) async {
|
||||||
|
try {
|
||||||
|
final loginResponse = await di<Openapi>().getAuthenticationApi().login(
|
||||||
|
loginCredentialDto: LoginCredentialDto((builder) {
|
||||||
|
builder.email = email;
|
||||||
|
builder.password = password;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return loginResponse.data?.accessToken;
|
||||||
|
} catch (e, s) {
|
||||||
|
log.severe("Exception occured while performing password login", e, s);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> oAuthLogin() async {
|
||||||
|
const String oAuthCallbackSchema = 'app.immich';
|
||||||
|
|
||||||
|
final oAuthApi = di<Openapi>().getOAuthApi();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final oAuthUrl = await oAuthApi.startOAuth(
|
||||||
|
oAuthConfigDto: OAuthConfigDto((builder) {
|
||||||
|
builder.redirectUri = "$oAuthCallbackSchema:/";
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
final oAuthUrlRes = oAuthUrl.data?.url;
|
||||||
|
if (oAuthUrlRes == null) {
|
||||||
|
log.severe(
|
||||||
|
"oAuth Server URL not available. Kindly ensure oAuth login is enabled in the server",
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final oAuthCallbackUrl = await FlutterWebAuth2.authenticate(
|
||||||
|
url: oAuthUrlRes,
|
||||||
|
callbackUrlScheme: oAuthCallbackSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
final loginResponse = await oAuthApi.finishOAuth(
|
||||||
|
oAuthCallbackDto: OAuthCallbackDto((builder) {
|
||||||
|
builder.url = oAuthCallbackUrl;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return loginResponse.data?.accessToken;
|
||||||
|
} catch (e) {
|
||||||
|
log.severe("Exception occured while performing oauth login", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
28
mobile-v2/lib/domain/services/user.service.dart
Normal file
28
mobile-v2/lib/domain/services/user.service.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
|
import 'package:openapi/openapi.dart';
|
||||||
|
|
||||||
|
class UserService with LogContext {
|
||||||
|
final Openapi _api;
|
||||||
|
|
||||||
|
UsersApi get _userApi => _api.getUsersApi();
|
||||||
|
|
||||||
|
UserService(this._api);
|
||||||
|
|
||||||
|
Future<User?> getMyUser() async {
|
||||||
|
try {
|
||||||
|
final response = await _userApi.getMyUser();
|
||||||
|
final dto = response.data;
|
||||||
|
if (dto == null) {
|
||||||
|
log.severe("Cannot fetch my user.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final preferences = await _userApi.getMyPreferences();
|
||||||
|
return User.fromAdminDto(dto, preferences.data);
|
||||||
|
} catch (e, s) {
|
||||||
|
log.severe("Error while fetching server features", e, s);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,90 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
|
||||||
|
|
||||||
class StoreKeyNotFoundException implements Exception {
|
|
||||||
final StoreKey key;
|
|
||||||
const StoreKeyNotFoundException(this.key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => "Key '${key.name}' not found in Store";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Key-value cache for individual items enumerated in StoreKey.
|
|
||||||
class StoreManager with LogContext {
|
|
||||||
late final IStoreRepository _db;
|
|
||||||
late final StreamSubscription _subscription;
|
|
||||||
final Map<int, dynamic> _cache = {};
|
|
||||||
|
|
||||||
StoreManager(IStoreRepository db) {
|
|
||||||
_db = db;
|
|
||||||
_subscription = _db.watchStore().listen(_onChangeListener);
|
|
||||||
_populateCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
_subscription.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _populateCache() async {
|
|
||||||
for (StoreKey key in StoreKey.values) {
|
|
||||||
final value = await _db.getValue(key);
|
|
||||||
if (value != null) {
|
|
||||||
_cache[key.id] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signal ready once the cache is populated
|
|
||||||
di.signalReady(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// clears all values from this store (cache and DB), only for testing!
|
|
||||||
Future<void> clear() async {
|
|
||||||
_cache.clear();
|
|
||||||
return await _db.clearStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the stored value for the given key (possibly null)
|
|
||||||
T? tryGet<T, U>(StoreKey<T, U> key) => _cache[key.id] as T?;
|
|
||||||
|
|
||||||
/// Returns the stored value for the given key or if null the [defaultValue]
|
|
||||||
/// Throws a [StoreKeyNotFoundException] if both are null
|
|
||||||
T get<T, U>(StoreKey<T, U> key, [T? defaultValue]) {
|
|
||||||
final value = _cache[key.id] ?? defaultValue;
|
|
||||||
if (value == null) {
|
|
||||||
throw StoreKeyNotFoundException(key);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Watches a specific key for changes
|
|
||||||
Stream<T?> watch<T, U>(StoreKey<T, U> key) => _db.watchValue(key);
|
|
||||||
|
|
||||||
/// Stores the value synchronously in the cache and asynchronously in the DB
|
|
||||||
FutureOr<bool> put<T, U>(StoreKey<T, U> key, T value) async {
|
|
||||||
if (_cache[key.id] == value) return Future.value(true);
|
|
||||||
_cache[key.id] = value;
|
|
||||||
return await _db.setValue(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the value synchronously from the cache and asynchronously from the DB
|
|
||||||
Future<void> delete<T, U>(StoreKey<T, U> key) async {
|
|
||||||
if (_cache[key.id] == null) return Future.value();
|
|
||||||
_cache.remove(key.id);
|
|
||||||
return await _db.deleteValue(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the state in cache if a value is updated in any isolate
|
|
||||||
void _onChangeListener(List<StoreValue>? data) {
|
|
||||||
if (data != null) {
|
|
||||||
for (StoreValue storeValue in data) {
|
|
||||||
if (storeValue.value != null) {
|
|
||||||
_cache[storeValue.id] = storeValue.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
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/models/user.model.dart';
|
||||||
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
|
||||||
class StoreEnumConverter<T extends Enum> extends IStoreConverter<T, int> {
|
class StoreEnumConverter<T extends Enum> extends IStoreConverter<T, int> {
|
||||||
const StoreEnumConverter(this.values);
|
const StoreEnumConverter(this.values);
|
||||||
@ -22,8 +27,8 @@ class StoreBooleanConverter extends IStoreConverter<bool, int> {
|
|||||||
int toPrimitive(bool value) => value ? 1 : 0;
|
int toPrimitive(bool value) => value ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class StorePrimitiveConverter<T> extends IStoreConverter<T, T> {
|
class _StorePrimitiveConverter<T> extends IStoreConverter<T, T> {
|
||||||
const StorePrimitiveConverter();
|
const _StorePrimitiveConverter();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
T fromPrimitive(T value) => value;
|
T fromPrimitive(T value) => value;
|
||||||
@ -31,3 +36,23 @@ class StorePrimitiveConverter<T> extends IStoreConverter<T, T> {
|
|||||||
@override
|
@override
|
||||||
T toPrimitive(T value) => value;
|
T toPrimitive(T value) => value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StoreStringConverter extends _StorePrimitiveConverter<String> {
|
||||||
|
const StoreStringConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoreIntConverter extends _StorePrimitiveConverter<int> {
|
||||||
|
const StoreIntConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoreUserConverter extends IStoreConverter<User, String> {
|
||||||
|
const StoreUserConverter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<User?> fromPrimitive(String value) async {
|
||||||
|
return await di<IUserRepository>().getUser(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toPrimitive(User value) => value.id;
|
||||||
|
}
|
||||||
|
@ -2,11 +2,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:immich_mobile/i18n/strings.g.dart';
|
import 'package:immich_mobile/i18n/strings.g.dart';
|
||||||
import 'package:immich_mobile/immich_app.dart';
|
import 'package:immich_mobile/immich_app.dart';
|
||||||
import 'package:immich_mobile/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/log_manager.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
// DI Injection
|
// DI Injection
|
||||||
ServiceLocator.configureServices();
|
ServiceLocator.configureServices();
|
||||||
|
// Init logging
|
||||||
|
LogManager.I.init();
|
||||||
// Init localization
|
// Init localization
|
||||||
LocaleSettings.useDeviceLocale();
|
LocaleSettings.useDeviceLocale();
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class ImLoadingIndicator extends StatelessWidget {
|
|||||||
width: dimension ?? 24,
|
width: dimension ?? 24,
|
||||||
height: dimension ?? 24,
|
height: dimension ?? 24,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
child: CircularProgressIndicator(strokeWidth: strokeWidth ?? 2),
|
child: CircularProgressIndicator(strokeWidth: strokeWidth ?? 4),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -48,8 +48,13 @@ class _ImSwitchListTileState<T> extends State<ImSwitchListTile<T>> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final value = _appSettingService.getSetting(widget.setting);
|
_appSettingService.getSetting(widget.setting).then((value) {
|
||||||
|
if (context.mounted) {
|
||||||
|
setState(() {
|
||||||
isEnabled = T != bool ? widget.fromAppSetting!(value) : value as bool;
|
isEnabled = T != bool ? widget.fromAppSetting!(value) : value as bool;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
|
|
||||||
|
class CurrentUserCubit extends Cubit<User> {
|
||||||
|
CurrentUserCubit(super.initialState);
|
||||||
|
}
|
@ -4,43 +4,51 @@ import 'package:flutter/material.dart';
|
|||||||
class LoginPageState {
|
class LoginPageState {
|
||||||
final bool isServerValidated;
|
final bool isServerValidated;
|
||||||
final bool isValidationInProgress;
|
final bool isValidationInProgress;
|
||||||
|
final bool isLoginSuccessful;
|
||||||
|
|
||||||
const LoginPageState({
|
const LoginPageState({
|
||||||
required this.isServerValidated,
|
required this.isServerValidated,
|
||||||
required this.isValidationInProgress,
|
required this.isValidationInProgress,
|
||||||
|
required this.isLoginSuccessful,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory LoginPageState.reset() {
|
factory LoginPageState.reset() {
|
||||||
return const LoginPageState(
|
return const LoginPageState(
|
||||||
isServerValidated: false,
|
isServerValidated: false,
|
||||||
isValidationInProgress: false,
|
isValidationInProgress: false,
|
||||||
|
isLoginSuccessful: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
LoginPageState copyWith({
|
LoginPageState copyWith({
|
||||||
bool? isServerValidated,
|
bool? isServerValidated,
|
||||||
bool? isValidationInProgress,
|
bool? isValidationInProgress,
|
||||||
|
bool? isLoginSuccessful,
|
||||||
}) {
|
}) {
|
||||||
return LoginPageState(
|
return LoginPageState(
|
||||||
isServerValidated: isServerValidated ?? this.isServerValidated,
|
isServerValidated: isServerValidated ?? this.isServerValidated,
|
||||||
isValidationInProgress:
|
isValidationInProgress:
|
||||||
isValidationInProgress ?? this.isValidationInProgress,
|
isValidationInProgress ?? this.isValidationInProgress,
|
||||||
|
isLoginSuccessful: isLoginSuccessful ?? this.isLoginSuccessful,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'LoginPageState(isServerValidated: $isServerValidated, isValidationInProgress: $isValidationInProgress)';
|
'LoginPageState(isServerValidated: $isServerValidated, isValidationInProgress: $isValidationInProgress, isLoginSuccessful: $isLoginSuccessful)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(covariant LoginPageState other) {
|
bool operator ==(covariant LoginPageState other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other.isServerValidated == isServerValidated &&
|
return other.isServerValidated == isServerValidated &&
|
||||||
other.isValidationInProgress == isValidationInProgress;
|
other.isValidationInProgress == isValidationInProgress &&
|
||||||
|
other.isLoginSuccessful == isLoginSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
isServerValidated.hashCode ^ isValidationInProgress.hashCode;
|
isServerValidated.hashCode ^
|
||||||
|
isValidationInProgress.hashCode ^
|
||||||
|
isLoginSuccessful.hashCode;
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,13 @@ class _LoginPageState extends State<LoginPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return BlocListener<LoginPageCubit, LoginPageState>(
|
||||||
|
listener: (_, loginState) {
|
||||||
|
if (loginState.isLoginSuccessful) {
|
||||||
|
context.replaceRoute(const TabControllerRoute());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
@ -161,6 +167,7 @@ class _LoginPageState extends State<LoginPage>
|
|||||||
secondaryBody: (_) => secondaryBody,
|
secondaryBody: (_) => secondaryBody,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/login.service.dart';
|
import 'package:immich_mobile/domain/services/login.service.dart';
|
||||||
import 'package:immich_mobile/domain/store_manager.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/common/states/server_info/server_feature_config.state.dart';
|
import 'package:immich_mobile/presentation/modules/common/states/server_info/server_feature_config.state.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/service_locator.dart';
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/immich_auth_interceptor.dart';
|
||||||
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
import 'package:immich_mobile/utils/snackbar_manager.dart';
|
import 'package:immich_mobile/utils/snackbar_manager.dart';
|
||||||
|
import 'package:openapi/openapi.dart';
|
||||||
|
|
||||||
class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
||||||
LoginPageCubit() : super(LoginPageState.reset());
|
LoginPageCubit() : super(LoginPageState.reset());
|
||||||
@ -60,8 +65,8 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
|||||||
// Check for /.well-known/immich
|
// Check for /.well-known/immich
|
||||||
url = await loginService.resolveEndpoint(uri);
|
url = await loginService.resolveEndpoint(uri);
|
||||||
|
|
||||||
di<StoreManager>().put(StoreKey.serverEndpoint, url);
|
di<IStoreRepository>().set(StoreKey.serverEndpoint, url);
|
||||||
ServiceLocator.registerPostValidationServices(url);
|
await ServiceLocator.registerPostValidationServices(url);
|
||||||
|
|
||||||
// Fetch server features
|
// Fetch server features
|
||||||
await di<ServerFeatureConfigCubit>().getFeatures();
|
await di<ServerFeatureConfigCubit>().getFeatures();
|
||||||
@ -76,15 +81,64 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
|
|||||||
required String email,
|
required String email,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
|
try {
|
||||||
emit(state.copyWith(isValidationInProgress: true));
|
emit(state.copyWith(isValidationInProgress: true));
|
||||||
|
final accessToken =
|
||||||
|
await di<LoginService>().passwordLogin(email, password);
|
||||||
|
|
||||||
final url = di<StoreManager>().get(StoreKey.serverEndpoint);
|
if (accessToken == null) {
|
||||||
|
SnackbarManager.showError(t.login.error.error_login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _postLogin(accessToken);
|
||||||
|
} finally {
|
||||||
|
emit(state.copyWith(isValidationInProgress: false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> oAuthLogin() async {
|
Future<void> oAuthLogin() async {
|
||||||
|
try {
|
||||||
emit(state.copyWith(isValidationInProgress: true));
|
emit(state.copyWith(isValidationInProgress: true));
|
||||||
|
|
||||||
final url = di<StoreManager>().get(StoreKey.serverEndpoint);
|
final accessToken = await di<LoginService>().oAuthLogin();
|
||||||
|
|
||||||
|
if (accessToken == null) {
|
||||||
|
SnackbarManager.showError(t.login.error.error_login_oauth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _postLogin(accessToken);
|
||||||
|
} finally {
|
||||||
|
emit(state.copyWith(isValidationInProgress: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _postLogin(String accessToken) async {
|
||||||
|
await di<IStoreRepository>().set(StoreKey.accessToken, accessToken);
|
||||||
|
|
||||||
|
/// Set token to interceptor
|
||||||
|
final interceptor = di<Openapi>()
|
||||||
|
.dio
|
||||||
|
.interceptors
|
||||||
|
.firstWhereOrNull((i) => i is ImmichAuthInterceptor)
|
||||||
|
as ImmichAuthInterceptor?;
|
||||||
|
interceptor?.setAccessToken(accessToken);
|
||||||
|
|
||||||
|
final user = await di<UserService>().getMyUser();
|
||||||
|
if (user == null) {
|
||||||
|
SnackbarManager.showError(t.login.error.error_login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register user
|
||||||
|
ServiceLocator.registerCurrentUser(user);
|
||||||
|
await di<IUserRepository>().insertUser(user);
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
isValidationInProgress: false,
|
||||||
|
isServerValidated: true,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetServerValidation() {
|
void resetServerValidation() {
|
||||||
|
@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.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/i18n/strings.g.dart';
|
import 'package:immich_mobile/i18n/strings.g.dart';
|
||||||
import 'package:immich_mobile/presentation/components/common/gap.widget.dart';
|
import 'package:immich_mobile/presentation/components/common/gap.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/components/common/loading_indaticator.widget.dart';
|
import 'package:immich_mobile/presentation/components/common/loading_indicator.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/components/input/filled_button.widget.dart';
|
import 'package:immich_mobile/presentation/components/input/filled_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/components/input/password_form_field.widget.dart';
|
import 'package:immich_mobile/presentation/components/input/password_form_field.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/components/input/text_button.widget.dart';
|
import 'package:immich_mobile/presentation/components/input/text_button.widget.dart';
|
||||||
@ -132,6 +132,7 @@ class _CredentialsPageState extends State<_CredentialsPage> {
|
|||||||
children: [
|
children: [
|
||||||
if (state.features.hasPasswordLogin) ...[
|
if (state.features.hasPasswordLogin) ...[
|
||||||
ImTextFormField(
|
ImTextFormField(
|
||||||
|
controller: widget.emailController,
|
||||||
label: context.t.login.label.email,
|
label: context.t.login.label.email,
|
||||||
isDisabled: isValidationInProgress,
|
isDisabled: isValidationInProgress,
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
@ -139,6 +140,7 @@ class _CredentialsPageState extends State<_CredentialsPage> {
|
|||||||
),
|
),
|
||||||
const SizedGap.mh(),
|
const SizedGap.mh(),
|
||||||
ImPasswordFormField(
|
ImPasswordFormField(
|
||||||
|
controller: widget.passwordController,
|
||||||
label: context.t.login.label.password,
|
label: context.t.login.label.password,
|
||||||
focusNode: passwordFocusNode,
|
focusNode: passwordFocusNode,
|
||||||
isDisabled: isValidationInProgress,
|
isDisabled: isValidationInProgress,
|
||||||
@ -148,12 +150,13 @@ class _CredentialsPageState extends State<_CredentialsPage> {
|
|||||||
ImFilledButton(
|
ImFilledButton(
|
||||||
label: context.t.login.label.login_button,
|
label: context.t.login.label.login_button,
|
||||||
icon: Symbols.login_rounded,
|
icon: Symbols.login_rounded,
|
||||||
onPressed: () =>
|
onPressed: () => unawaited(
|
||||||
context.read<LoginPageCubit>().passwordLogin(
|
context.read<LoginPageCubit>().passwordLogin(
|
||||||
email: widget.emailController.text,
|
email: widget.emailController.text,
|
||||||
password: widget.passwordController.text,
|
password: widget.passwordController.text,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
// Divider when both password and oAuth login is enabled
|
// Divider when both password and oAuth login is enabled
|
||||||
if (state.features.hasOAuthLogin) const Divider(),
|
if (state.features.hasOAuthLogin) const Divider(),
|
||||||
],
|
],
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
import 'package:immich_mobile/domain/interfaces/log.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/models/user.model.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/log.repository.dart';
|
import 'package:immich_mobile/domain/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/domain/repositories/store.repository.dart';
|
import 'package:immich_mobile/domain/repositories/store.repository.dart';
|
||||||
|
import 'package:immich_mobile/domain/repositories/user.repository.dart';
|
||||||
import 'package:immich_mobile/domain/services/app_setting.service.dart';
|
import 'package:immich_mobile/domain/services/app_setting.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/server_info.service.dart';
|
||||||
import 'package:immich_mobile/domain/store_manager.dart';
|
import 'package:immich_mobile/presentation/modules/common/states/current_user.state.dart';
|
||||||
import 'package:immich_mobile/presentation/modules/common/states/server_info/server_feature_config.state.dart';
|
import 'package:immich_mobile/presentation/modules/common/states/server_info/server_feature_config.state.dart';
|
||||||
import 'package:immich_mobile/presentation/modules/theme/states/app_theme.state.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/utils/immich_auth_interceptor.dart';
|
||||||
import 'package:openapi/openapi.dart';
|
import 'package:openapi/openapi.dart';
|
||||||
|
|
||||||
final di = GetIt.I;
|
final di = GetIt.I;
|
||||||
@ -29,12 +38,12 @@ class ServiceLocator {
|
|||||||
|
|
||||||
// Init store
|
// Init store
|
||||||
di.registerFactory<IStoreRepository>(() => StoreDriftRepository(di()));
|
di.registerFactory<IStoreRepository>(() => StoreDriftRepository(di()));
|
||||||
// StoreManager populates its cache with a async gap, manually signalReady once the cache is populated.
|
|
||||||
di.registerSingleton<StoreManager>(StoreManager(di()), signalsReady: true);
|
|
||||||
// Logs
|
// Logs
|
||||||
di.registerFactory<ILogRepository>(() => LogDriftRepository(di()));
|
di.registerFactory<ILogRepository>(() => LogDriftRepository(di()));
|
||||||
// App Settings
|
// App Settings
|
||||||
di.registerFactory<AppSettingService>(() => AppSettingService(di()));
|
di.registerFactory<AppSettingService>(() => AppSettingService(di()));
|
||||||
|
// User Repo
|
||||||
|
di.registerFactory<IUserRepository>(() => UserDriftRepository(di()));
|
||||||
// Login Service
|
// Login Service
|
||||||
di.registerFactory<LoginService>(() => const LoginService());
|
di.registerFactory<LoginService>(() => const LoginService());
|
||||||
|
|
||||||
@ -46,17 +55,35 @@ class ServiceLocator {
|
|||||||
di.registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
|
di.registerLazySingleton<AppThemeCubit>(() => AppThemeCubit(di()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerPostValidationServices(String endpoint) {
|
static FutureOr<void> registerPostValidationServices(String endpoint) async {
|
||||||
if (di.isRegistered<Openapi>()) {
|
if (di.isRegistered<Openapi>()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final deviceInfo = DeviceInfoPlugin();
|
||||||
|
final String deviceModel;
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
deviceModel = (await deviceInfo.iosInfo).utsname.machine;
|
||||||
|
} else {
|
||||||
|
deviceModel = (await deviceInfo.androidInfo).model;
|
||||||
|
}
|
||||||
|
|
||||||
// ====== DOMAIN
|
// ====== DOMAIN
|
||||||
|
|
||||||
di.registerSingleton<Openapi>(
|
di.registerSingleton<Openapi>(
|
||||||
Openapi(
|
Openapi(
|
||||||
basePathOverride: endpoint,
|
dio: Dio(
|
||||||
interceptors: [BearerAuthInterceptor()],
|
BaseOptions(
|
||||||
|
baseUrl: endpoint,
|
||||||
|
connectTimeout: const Duration(milliseconds: 5000),
|
||||||
|
receiveTimeout: const Duration(milliseconds: 3000),
|
||||||
|
headers: {
|
||||||
|
'deviceModel': deviceModel,
|
||||||
|
'deviceType': Platform.operatingSystem,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
interceptors: [ImmichAuthInterceptor()],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
di.registerFactory<ServerInfoService>(() => ServerInfoService(di()));
|
di.registerFactory<ServerInfoService>(() => ServerInfoService(di()));
|
||||||
@ -67,4 +94,8 @@ class ServiceLocator {
|
|||||||
() => ServerFeatureConfigCubit(di()),
|
() => ServerFeatureConfigCubit(di()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void registerCurrentUser(User user) {
|
||||||
|
di.registerSingleton<CurrentUserCubit>(CurrentUserCubit(user));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,11 +36,16 @@ class Assets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AssetGenImage {
|
class AssetGenImage {
|
||||||
const AssetGenImage(this._assetName, {this.size = null});
|
const AssetGenImage(
|
||||||
|
this._assetName, {
|
||||||
|
this.size,
|
||||||
|
this.flavors = const {},
|
||||||
|
});
|
||||||
|
|
||||||
final String _assetName;
|
final String _assetName;
|
||||||
|
|
||||||
final Size? size;
|
final Size? size;
|
||||||
|
final Set<String> flavors;
|
||||||
|
|
||||||
Image image({
|
Image image({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Log messages stored in the DB
|
||||||
|
const int kLogMessageLimit = 500;
|
||||||
|
|
||||||
/// Global ScaffoldMessengerKey to show snackbars
|
/// Global ScaffoldMessengerKey to show snackbars
|
||||||
final GlobalKey<ScaffoldMessengerState> kScafMessengerKey = GlobalKey();
|
final GlobalKey<ScaffoldMessengerState> kScafMessengerKey = GlobalKey();
|
||||||
|
29
mobile-v2/lib/utils/immich_auth_interceptor.dart
Normal file
29
mobile-v2/lib/utils/immich_auth_interceptor.dart
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:immich_mobile/presentation/router/router.dart';
|
||||||
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
|
||||||
|
|
||||||
|
class ImmichAuthInterceptor extends Interceptor with LogContext {
|
||||||
|
String? _accessToken;
|
||||||
|
|
||||||
|
void setAccessToken(String token) => _accessToken = token;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
|
if (_accessToken != null) {
|
||||||
|
options.headers["x-immich-user-token"] = _accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.next(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
|
if (response.statusCode == 401) {
|
||||||
|
log.severe("Token expired. Logging user out");
|
||||||
|
di<AppRouter>().replaceAll([const LoginRoute()]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler.next(response);
|
||||||
|
}
|
||||||
|
}
|
75
mobile-v2/lib/utils/log_manager.dart
Normal file
75
mobile-v2/lib/utils/log_manager.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
|
import 'package:immich_mobile/service_locator.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
/// [LogManager] is a custom logger that is built on top of the [logging] package.
|
||||||
|
/// The logs are written to the database and onto console, using `debugPrint` method.
|
||||||
|
///
|
||||||
|
/// The logs are deleted when exceeding the `maxLogEntries` (default 500) property
|
||||||
|
/// in the class.
|
||||||
|
class LogManager {
|
||||||
|
LogManager._();
|
||||||
|
static final LogManager _instance = LogManager._();
|
||||||
|
|
||||||
|
// ignore: match-getter-setter-field-names
|
||||||
|
static LogManager get I => _instance;
|
||||||
|
|
||||||
|
List<LogMessage> _msgBuffer = [];
|
||||||
|
Timer? _timer;
|
||||||
|
late StreamSubscription<LogRecord> _subscription;
|
||||||
|
|
||||||
|
void _onLogRecord(LogRecord record) {
|
||||||
|
// Only print in development
|
||||||
|
assert(() {
|
||||||
|
debugPrint('[${record.level.name}] [${record.time}] ${record.message}');
|
||||||
|
if (record.error != null && record.stackTrace != null) {
|
||||||
|
debugPrint('${record.error}');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
final lm = LogMessage(
|
||||||
|
logger: record.loggerName,
|
||||||
|
content: record.message,
|
||||||
|
level: record.level.toLogLevel(),
|
||||||
|
createdAt: record.time,
|
||||||
|
error: record.error?.toString(),
|
||||||
|
stack: record.stackTrace?.toString(),
|
||||||
|
);
|
||||||
|
_msgBuffer.add(lm);
|
||||||
|
|
||||||
|
// delayed batch writing to database: increases performance when logging
|
||||||
|
// messages in quick succession and reduces NAND wear
|
||||||
|
_timer ??= Timer(const Duration(seconds: 5), _flushBufferToDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _flushBufferToDatabase() {
|
||||||
|
_timer = null;
|
||||||
|
final buffer = _msgBuffer;
|
||||||
|
_msgBuffer = [];
|
||||||
|
di<ILogRepository>().addAll(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
_subscription = Logger.root.onRecord.listen(_onLogRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateLevel(LogLevel level) {
|
||||||
|
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_subscription.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearLogs() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
_msgBuffer.clear();
|
||||||
|
di<ILogRepository>().clear();
|
||||||
|
}
|
||||||
|
}
|
@ -4,5 +4,5 @@ import 'package:logging/logging.dart';
|
|||||||
mixin LogContext {
|
mixin LogContext {
|
||||||
@protected
|
@protected
|
||||||
@nonVirtual
|
@nonVirtual
|
||||||
Logger get log => Logger.detached(runtimeType.toString());
|
Logger get log => Logger(runtimeType.toString());
|
||||||
}
|
}
|
||||||
|
@ -45,18 +45,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: auto_route
|
name: auto_route
|
||||||
sha256: "878186aae276296bf1cfc0a02cd2788cfb473eb622e0f5e4293f40ecdf86d80d"
|
sha256: a9001a90539ca3effc168f7e1029a5885c7326b9032c09ac895e303c1d137704
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.0"
|
version: "8.3.0"
|
||||||
auto_route_generator:
|
auto_route_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: auto_route_generator
|
name: auto_route_generator
|
||||||
sha256: ba28133d3a3bf0a66772bcc98dade5843753cd9f1a8fb4802b842895515b67d3
|
sha256: a21d7a936c917488653c972f62d884d8adcf8c5d37acc7cd24da33cf784546c0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.0"
|
version: "8.1.0"
|
||||||
bloc:
|
bloc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -241,30 +241,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
device_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: device_info_plus
|
||||||
|
sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.1.0"
|
||||||
|
device_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus_platform_interface
|
||||||
|
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5"
|
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.3+1"
|
version: "5.5.0+1"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
drift:
|
drift:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift
|
name: drift
|
||||||
sha256: "6acedc562ffeed308049f78fb1906abad3d65714580b6745441ee6d50ec564cd"
|
sha256: "4e0ffee40d23f0b809e6cff1ad202886f51d629649073ed42d9cd1d194ea943e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.18.0"
|
version: "2.19.1+1"
|
||||||
drift_dev:
|
drift_dev:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: drift_dev
|
name: drift_dev
|
||||||
sha256: d9b020736ea85fff1568699ce18b89fabb3f0f042e8a7a05e84a3ec20d39acde
|
sha256: ac7647c6cedca99724ca300cff9181f6dd799428f8ed71f94159ed0528eaec26
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.18.0"
|
version: "2.19.1"
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -314,10 +338,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_adaptive_scaffold
|
name: flutter_adaptive_scaffold
|
||||||
sha256: "794791e6fc0cc23e375d07ea987e76cf7f0eb7cd753532e45dc35b7e90575e2d"
|
sha256: "56d4d81fe88ecffe8ae96b8d89a1ae793c0a85035bb9b74ff28f20eea0cdbdc2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.11"
|
version: "0.1.11+1"
|
||||||
flutter_bloc:
|
flutter_bloc:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -330,26 +354,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_gen_core
|
name: flutter_gen_core
|
||||||
sha256: b9894396b2a790cc2d6eb3ed86e5e113aaed993765b21d4b981c9da4476e0f52
|
sha256: d8e828ad015a8511624491b78ad8e3f86edb7993528b1613aefbb4ad95947795
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.5.0+1"
|
version: "5.6.0"
|
||||||
flutter_gen_runner:
|
flutter_gen_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_gen_runner
|
name: flutter_gen_runner
|
||||||
sha256: b4c4c54e4dd89022f5e405fe96f16781be2dfbeabe8a70ccdf73b7af1302c655
|
sha256: "931b03f77c164df0a4815aac0efc619a6ac8ec4cada55025119fca4894dada90"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.5.0+1"
|
version: "5.6.0"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "4.0.0"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -360,6 +384,22 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_auth_2:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_web_auth_2
|
||||||
|
sha256: "4d3d2fd3d26bf1a26b3beafd4b4b899c0ffe10dc99af25abc58ffe24e991133c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
flutter_web_auth_2_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_web_auth_2_platform_interface
|
||||||
|
sha256: e8669e262005a8354389ba2971f0fc1c36188481234ff50d013aaf993f30f739
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -393,10 +433,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: graphs
|
name: graphs
|
||||||
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
hashcodes:
|
hashcodes:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -409,10 +449,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -505,10 +545,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -537,10 +577,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: material_symbols_icons
|
name: material_symbols_icons
|
||||||
sha256: a2c78726048c755f0f90fd2b7c8799cd94338e2e9b7ab6498ae56503262c14bc
|
sha256: "37f88057af06224cd99242bd9b5ceda8c1ebddfff67bd5e8432521910a3d4598"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2762.0"
|
version: "4.2771.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -640,10 +680,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514"
|
sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.5"
|
version: "2.2.7"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -672,10 +712,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.3.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -688,10 +728,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: photo_manager
|
name: photo_manager
|
||||||
sha256: "68d6099d07ce5033170f8368af8128a4555cf1d590a97242f83669552de989b1"
|
sha256: "2f98fed8fede27eaf55021a1ce382609a715b52096a94a315f99ae33b6d2eaab"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.2"
|
||||||
photo_manager_image_provider:
|
photo_manager_image_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -789,10 +829,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: slang
|
name: slang
|
||||||
sha256: "0d8a8cbfd7858ed2bd9164a79bfb664ea83f1e124740b28acd0618757fc87ecc"
|
sha256: f68f6d6709890f85efabfb0318e9d694be2ebdd333e57fe5cb50eee449e4e3ab
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.31.0"
|
version: "3.31.1"
|
||||||
slang_build_runner:
|
slang_build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -829,26 +869,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295
|
sha256: "6d17989c0b06a5870b2190d391925186f944cb943e5262d0d3f778fcfca3bc6e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.4"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: "9f89a7e7dc36eac2035808427eba1c3fbd79e59c3a22093d8dace6d36b1fe89e"
|
sha256: "62bbb4073edbcdf53f40c80775f33eea01d301b7b81417e5b3fb7395416258c1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.23"
|
version: "0.5.24"
|
||||||
sqlparser:
|
sqlparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlparser
|
name: sqlparser
|
||||||
sha256: ade9a67fd70d0369329ed3373208de7ebd8662470e8c396fc8d0d60f9acdfc9f
|
sha256: "3be52b4968fc2f098ba735863404756d2fe3ea0729cf006a5b5612618f74ca04"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.36.0"
|
version: "0.37.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -933,18 +973,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
|
sha256: "95d8027db36a0e52caf55680f91e33ea6aa12a3ce608c90b06f4e429a21067ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.3"
|
version: "6.3.5"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
|
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.1"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -981,10 +1021,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.2"
|
||||||
vector_graphics_codec:
|
vector_graphics_codec:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1037,18 +1077,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket
|
name: web_socket
|
||||||
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5"
|
version: "0.1.6"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1057,6 +1097,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.5.1"
|
version: "5.5.1"
|
||||||
|
win32_registry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32_registry
|
||||||
|
sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
window_to_front:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: window_to_front
|
||||||
|
sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.3"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -48,6 +48,9 @@ dependencies:
|
|||||||
url_launcher: ^6.3.0
|
url_launcher: ^6.3.0
|
||||||
# plus_extensions
|
# plus_extensions
|
||||||
package_info_plus: ^8.0.0
|
package_info_plus: ^8.0.0
|
||||||
|
device_info_plus: ^10.1.0
|
||||||
|
# oauth login
|
||||||
|
flutter_web_auth_2: ^3.1.2
|
||||||
|
|
||||||
openapi:
|
openapi:
|
||||||
path: openapi
|
path: openapi
|
||||||
@ -57,7 +60,7 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# Recommended lints
|
# Recommended lints
|
||||||
flutter_lints: ^3.0.0
|
flutter_lints: ^4.0.0
|
||||||
# Code generator
|
# Code generator
|
||||||
build_runner: ^2.4.9
|
build_runner: ^2.4.9
|
||||||
# Database helper
|
# Database helper
|
||||||
@ -67,7 +70,7 @@ dev_dependencies:
|
|||||||
# Localization generator
|
# Localization generator
|
||||||
slang_build_runner: ^3.30.0
|
slang_build_runner: ^3.30.0
|
||||||
# Assets constant generator
|
# Assets constant generator
|
||||||
flutter_gen_runner: 5.5.0+1
|
flutter_gen_runner: ^5.6.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user