Compare commits

...

3 Commits

Author SHA1 Message Date
Alex Tran
b24637ba74 update schema 2025-06-28 22:20:02 -05:00
Alex Tran
d54def39ca drift logs 2025-06-28 17:50:08 -05:00
Alex Tran
f0c9163364 feat: drift store migration 2025-06-27 15:40:48 -05:00
24 changed files with 1682 additions and 281 deletions

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@ import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/drift_store.repository.dart';
import 'package:logging/logging.dart';
/// Service responsible for handling application logging.
@@ -14,8 +14,8 @@ import 'package:logging/logging.dart';
/// writes them to a persistent [ILogRepository], and manages log levels
/// via [IStoreRepository]
class LogService {
final IsarLogRepository _logRepository;
final IsarStoreRepository _storeRepository;
final LogRepository _logRepository;
final IStoreRepository _storeRepository;
final List<LogMessage> _msgBuffer = [];
@@ -37,8 +37,8 @@ class LogService {
}
static Future<LogService> init({
required IsarLogRepository logRepository,
required IsarStoreRepository storeRepository,
required LogRepository logRepository,
required IStoreRepository storeRepository,
bool shouldBuffer = true,
}) async {
_instance ??= await create(
@@ -50,8 +50,8 @@ class LogService {
}
static Future<LogService> create({
required IsarLogRepository logRepository,
required IsarStoreRepository storeRepository,
required LogRepository logRepository,
required IStoreRepository storeRepository,
bool shouldBuffer = true,
}) async {
final instance = LogService._(logRepository, storeRepository, shouldBuffer);

View File

@@ -1,18 +1,18 @@
import 'dart:async';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/drift_store.repository.dart';
/// Provides access to a persistent key-value store with an in-memory cache.
/// Listens for repository changes to keep the cache updated.
class StoreService {
final IsarStoreRepository _storeRepository;
final IStoreRepository _storeRepository;
/// In-memory cache. Keys are [StoreKey.id]
final Map<int, Object?> _cache = {};
late final StreamSubscription<StoreDto> _storeUpdateSubscription;
StoreService._({required IsarStoreRepository storeRepository})
StoreService._({required IStoreRepository storeRepository})
: _storeRepository = storeRepository;
// TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider
@@ -26,14 +26,14 @@ class StoreService {
// TODO: Replace the implementation with the one from create after removing the typedef
static Future<StoreService> init({
required IsarStoreRepository storeRepository,
required IStoreRepository storeRepository,
}) async {
_instance ??= await create(storeRepository: storeRepository);
return _instance!;
}
static Future<StoreService> create({
required IsarStoreRepository storeRepository,
required IStoreRepository storeRepository,
}) async {
final instance = StoreService._(storeRepository: storeRepository);
await instance._populateCache();

View File

@@ -0,0 +1,13 @@
import 'package:isar/isar.dart';
part 'isar_store.entity.g.dart';
/// Internal class for `Store`, do not use elsewhere.
@Collection(inheritance: false)
class StoreValue {
final Id id;
final int? intValue;
final String? strValue;
const StoreValue(this.id, {this.intValue, this.strValue});
}

View File

@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'store.entity.dart';
part of 'isar_store.entity.dart';
// **************************************************************************
// IsarCollectionGenerator

View File

@@ -1,5 +1,7 @@
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
import 'package:isar/isar.dart';
import 'package:drift/drift.dart';
part 'log.entity.g.dart';
@@ -45,3 +47,21 @@ class LoggerMessage {
);
}
}
class LoggerMessageEntity extends Table with DriftDefaultsMixin {
const LoggerMessageEntity();
IntColumn get id => integer().autoIncrement()();
TextColumn get message => text()();
TextColumn get details => text().nullable()();
IntColumn get level => intEnum<LogLevel>()();
DateTimeColumn get createdAt => dateTime()();
TextColumn get context1 => text().nullable()();
TextColumn get context2 => text().nullable()();
}

View File

@@ -0,0 +1,589 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart'
as i1;
import 'package:immich_mobile/domain/models/log.model.dart' as i2;
import 'package:immich_mobile/infrastructure/entities/log.entity.dart' as i3;
typedef $$LoggerMessageEntityTableCreateCompanionBuilder
= i1.LoggerMessageEntityCompanion Function({
required int id,
required String message,
i0.Value<String?> details,
required i2.LogLevel level,
required DateTime createdAt,
i0.Value<String?> context1,
i0.Value<String?> context2,
});
typedef $$LoggerMessageEntityTableUpdateCompanionBuilder
= i1.LoggerMessageEntityCompanion Function({
i0.Value<int> id,
i0.Value<String> message,
i0.Value<String?> details,
i0.Value<i2.LogLevel> level,
i0.Value<DateTime> createdAt,
i0.Value<String?> context1,
i0.Value<String?> context2,
});
class $$LoggerMessageEntityTableFilterComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$LoggerMessageEntityTable> {
$$LoggerMessageEntityTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnFilters<int> get id => $composableBuilder(
column: $table.id, builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<String> get message => $composableBuilder(
column: $table.message, builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<String> get details => $composableBuilder(
column: $table.details, builder: (column) => i0.ColumnFilters(column));
i0.ColumnWithTypeConverterFilters<i2.LogLevel, i2.LogLevel, int> get level =>
$composableBuilder(
column: $table.level,
builder: (column) => i0.ColumnWithTypeConverterFilters(column));
i0.ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<String> get context1 => $composableBuilder(
column: $table.context1, builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<String> get context2 => $composableBuilder(
column: $table.context2, builder: (column) => i0.ColumnFilters(column));
}
class $$LoggerMessageEntityTableOrderingComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$LoggerMessageEntityTable> {
$$LoggerMessageEntityTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnOrderings<int> get id => $composableBuilder(
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<String> get message => $composableBuilder(
column: $table.message, builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<String> get details => $composableBuilder(
column: $table.details, builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<int> get level => $composableBuilder(
column: $table.level, builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<String> get context1 => $composableBuilder(
column: $table.context1, builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<String> get context2 => $composableBuilder(
column: $table.context2, builder: (column) => i0.ColumnOrderings(column));
}
class $$LoggerMessageEntityTableAnnotationComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$LoggerMessageEntityTable> {
$$LoggerMessageEntityTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.GeneratedColumn<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
i0.GeneratedColumn<String> get message =>
$composableBuilder(column: $table.message, builder: (column) => column);
i0.GeneratedColumn<String> get details =>
$composableBuilder(column: $table.details, builder: (column) => column);
i0.GeneratedColumnWithTypeConverter<i2.LogLevel, int> get level =>
$composableBuilder(column: $table.level, builder: (column) => column);
i0.GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
i0.GeneratedColumn<String> get context1 =>
$composableBuilder(column: $table.context1, builder: (column) => column);
i0.GeneratedColumn<String> get context2 =>
$composableBuilder(column: $table.context2, builder: (column) => column);
}
class $$LoggerMessageEntityTableTableManager extends i0.RootTableManager<
i0.GeneratedDatabase,
i1.$LoggerMessageEntityTable,
i1.LoggerMessageEntityData,
i1.$$LoggerMessageEntityTableFilterComposer,
i1.$$LoggerMessageEntityTableOrderingComposer,
i1.$$LoggerMessageEntityTableAnnotationComposer,
$$LoggerMessageEntityTableCreateCompanionBuilder,
$$LoggerMessageEntityTableUpdateCompanionBuilder,
(
i1.LoggerMessageEntityData,
i0.BaseReferences<i0.GeneratedDatabase, i1.$LoggerMessageEntityTable,
i1.LoggerMessageEntityData>
),
i1.LoggerMessageEntityData,
i0.PrefetchHooks Function()> {
$$LoggerMessageEntityTableTableManager(
i0.GeneratedDatabase db, i1.$LoggerMessageEntityTable table)
: super(i0.TableManagerState(
db: db,
table: table,
createFilteringComposer: () => i1
.$$LoggerMessageEntityTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
i1.$$LoggerMessageEntityTableOrderingComposer(
$db: db, $table: table),
createComputedFieldComposer: () =>
i1.$$LoggerMessageEntityTableAnnotationComposer(
$db: db, $table: table),
updateCompanionCallback: ({
i0.Value<int> id = const i0.Value.absent(),
i0.Value<String> message = const i0.Value.absent(),
i0.Value<String?> details = const i0.Value.absent(),
i0.Value<i2.LogLevel> level = const i0.Value.absent(),
i0.Value<DateTime> createdAt = const i0.Value.absent(),
i0.Value<String?> context1 = const i0.Value.absent(),
i0.Value<String?> context2 = const i0.Value.absent(),
}) =>
i1.LoggerMessageEntityCompanion(
id: id,
message: message,
details: details,
level: level,
createdAt: createdAt,
context1: context1,
context2: context2,
),
createCompanionCallback: ({
required int id,
required String message,
i0.Value<String?> details = const i0.Value.absent(),
required i2.LogLevel level,
required DateTime createdAt,
i0.Value<String?> context1 = const i0.Value.absent(),
i0.Value<String?> context2 = const i0.Value.absent(),
}) =>
i1.LoggerMessageEntityCompanion.insert(
id: id,
message: message,
details: details,
level: level,
createdAt: createdAt,
context1: context1,
context2: context2,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
));
}
typedef $$LoggerMessageEntityTableProcessedTableManager
= i0.ProcessedTableManager<
i0.GeneratedDatabase,
i1.$LoggerMessageEntityTable,
i1.LoggerMessageEntityData,
i1.$$LoggerMessageEntityTableFilterComposer,
i1.$$LoggerMessageEntityTableOrderingComposer,
i1.$$LoggerMessageEntityTableAnnotationComposer,
$$LoggerMessageEntityTableCreateCompanionBuilder,
$$LoggerMessageEntityTableUpdateCompanionBuilder,
(
i1.LoggerMessageEntityData,
i0.BaseReferences<i0.GeneratedDatabase, i1.$LoggerMessageEntityTable,
i1.LoggerMessageEntityData>
),
i1.LoggerMessageEntityData,
i0.PrefetchHooks Function()>;
class $LoggerMessageEntityTable extends i3.LoggerMessageEntity
with i0.TableInfo<$LoggerMessageEntityTable, i1.LoggerMessageEntityData> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
$LoggerMessageEntityTable(this.attachedDatabase, [this._alias]);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<int> id = i0.GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: i0.DriftSqlType.int,
requiredDuringInsert: true,
defaultConstraints:
i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
static const i0.VerificationMeta _messageMeta =
const i0.VerificationMeta('message');
@override
late final i0.GeneratedColumn<String> message = i0.GeneratedColumn<String>(
'message', aliasedName, false,
type: i0.DriftSqlType.string, requiredDuringInsert: true);
static const i0.VerificationMeta _detailsMeta =
const i0.VerificationMeta('details');
@override
late final i0.GeneratedColumn<String> details = i0.GeneratedColumn<String>(
'details', aliasedName, true,
type: i0.DriftSqlType.string, requiredDuringInsert: false);
@override
late final i0.GeneratedColumnWithTypeConverter<i2.LogLevel, int> level =
i0.GeneratedColumn<int>('level', aliasedName, false,
type: i0.DriftSqlType.int, requiredDuringInsert: true)
.withConverter<i2.LogLevel>(
i1.$LoggerMessageEntityTable.$converterlevel);
static const i0.VerificationMeta _createdAtMeta =
const i0.VerificationMeta('createdAt');
@override
late final i0.GeneratedColumn<DateTime> createdAt =
i0.GeneratedColumn<DateTime>('created_at', aliasedName, false,
type: i0.DriftSqlType.dateTime, requiredDuringInsert: true);
static const i0.VerificationMeta _context1Meta =
const i0.VerificationMeta('context1');
@override
late final i0.GeneratedColumn<String> context1 = i0.GeneratedColumn<String>(
'context1', aliasedName, true,
type: i0.DriftSqlType.string, requiredDuringInsert: false);
static const i0.VerificationMeta _context2Meta =
const i0.VerificationMeta('context2');
@override
late final i0.GeneratedColumn<String> context2 = i0.GeneratedColumn<String>(
'context2', aliasedName, true,
type: i0.DriftSqlType.string, requiredDuringInsert: false);
@override
List<i0.GeneratedColumn> get $columns =>
[id, message, details, level, createdAt, context1, context2];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'logger_message_entity';
@override
i0.VerificationContext validateIntegrity(
i0.Insertable<i1.LoggerMessageEntityData> instance,
{bool isInserting = false}) {
final context = i0.VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
} else if (isInserting) {
context.missing(_idMeta);
}
if (data.containsKey('message')) {
context.handle(_messageMeta,
message.isAcceptableOrUnknown(data['message']!, _messageMeta));
} else if (isInserting) {
context.missing(_messageMeta);
}
if (data.containsKey('details')) {
context.handle(_detailsMeta,
details.isAcceptableOrUnknown(data['details']!, _detailsMeta));
}
if (data.containsKey('created_at')) {
context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
} else if (isInserting) {
context.missing(_createdAtMeta);
}
if (data.containsKey('context1')) {
context.handle(_context1Meta,
context1.isAcceptableOrUnknown(data['context1']!, _context1Meta));
}
if (data.containsKey('context2')) {
context.handle(_context2Meta,
context2.isAcceptableOrUnknown(data['context2']!, _context2Meta));
}
return context;
}
@override
Set<i0.GeneratedColumn> get $primaryKey => {id};
@override
i1.LoggerMessageEntityData map(Map<String, dynamic> data,
{String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.LoggerMessageEntityData(
id: attachedDatabase.typeMapping
.read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!,
message: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}message'])!,
details: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}details']),
level: i1.$LoggerMessageEntityTable.$converterlevel.fromSql(
attachedDatabase.typeMapping
.read(i0.DriftSqlType.int, data['${effectivePrefix}level'])!),
createdAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
context1: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}context1']),
context2: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}context2']),
);
}
@override
$LoggerMessageEntityTable createAlias(String alias) {
return $LoggerMessageEntityTable(attachedDatabase, alias);
}
static i0.JsonTypeConverter2<i2.LogLevel, int, int> $converterlevel =
const i0.EnumIndexConverter<i2.LogLevel>(i2.LogLevel.values);
@override
bool get withoutRowId => true;
@override
bool get isStrict => true;
}
class LoggerMessageEntityData extends i0.DataClass
implements i0.Insertable<i1.LoggerMessageEntityData> {
final int id;
final String message;
final String? details;
final i2.LogLevel level;
final DateTime createdAt;
final String? context1;
final String? context2;
const LoggerMessageEntityData(
{required this.id,
required this.message,
this.details,
required this.level,
required this.createdAt,
this.context1,
this.context2});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
map['id'] = i0.Variable<int>(id);
map['message'] = i0.Variable<String>(message);
if (!nullToAbsent || details != null) {
map['details'] = i0.Variable<String>(details);
}
{
map['level'] = i0.Variable<int>(
i1.$LoggerMessageEntityTable.$converterlevel.toSql(level));
}
map['created_at'] = i0.Variable<DateTime>(createdAt);
if (!nullToAbsent || context1 != null) {
map['context1'] = i0.Variable<String>(context1);
}
if (!nullToAbsent || context2 != null) {
map['context2'] = i0.Variable<String>(context2);
}
return map;
}
factory LoggerMessageEntityData.fromJson(Map<String, dynamic> json,
{i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return LoggerMessageEntityData(
id: serializer.fromJson<int>(json['id']),
message: serializer.fromJson<String>(json['message']),
details: serializer.fromJson<String?>(json['details']),
level: i1.$LoggerMessageEntityTable.$converterlevel
.fromJson(serializer.fromJson<int>(json['level'])),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
context1: serializer.fromJson<String?>(json['context1']),
context2: serializer.fromJson<String?>(json['context2']),
);
}
@override
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'message': serializer.toJson<String>(message),
'details': serializer.toJson<String?>(details),
'level': serializer.toJson<int>(
i1.$LoggerMessageEntityTable.$converterlevel.toJson(level)),
'createdAt': serializer.toJson<DateTime>(createdAt),
'context1': serializer.toJson<String?>(context1),
'context2': serializer.toJson<String?>(context2),
};
}
i1.LoggerMessageEntityData copyWith(
{int? id,
String? message,
i0.Value<String?> details = const i0.Value.absent(),
i2.LogLevel? level,
DateTime? createdAt,
i0.Value<String?> context1 = const i0.Value.absent(),
i0.Value<String?> context2 = const i0.Value.absent()}) =>
i1.LoggerMessageEntityData(
id: id ?? this.id,
message: message ?? this.message,
details: details.present ? details.value : this.details,
level: level ?? this.level,
createdAt: createdAt ?? this.createdAt,
context1: context1.present ? context1.value : this.context1,
context2: context2.present ? context2.value : this.context2,
);
LoggerMessageEntityData copyWithCompanion(
i1.LoggerMessageEntityCompanion data) {
return LoggerMessageEntityData(
id: data.id.present ? data.id.value : this.id,
message: data.message.present ? data.message.value : this.message,
details: data.details.present ? data.details.value : this.details,
level: data.level.present ? data.level.value : this.level,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
context1: data.context1.present ? data.context1.value : this.context1,
context2: data.context2.present ? data.context2.value : this.context2,
);
}
@override
String toString() {
return (StringBuffer('LoggerMessageEntityData(')
..write('id: $id, ')
..write('message: $message, ')
..write('details: $details, ')
..write('level: $level, ')
..write('createdAt: $createdAt, ')
..write('context1: $context1, ')
..write('context2: $context2')
..write(')'))
.toString();
}
@override
int get hashCode =>
Object.hash(id, message, details, level, createdAt, context1, context2);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.LoggerMessageEntityData &&
other.id == this.id &&
other.message == this.message &&
other.details == this.details &&
other.level == this.level &&
other.createdAt == this.createdAt &&
other.context1 == this.context1 &&
other.context2 == this.context2);
}
class LoggerMessageEntityCompanion
extends i0.UpdateCompanion<i1.LoggerMessageEntityData> {
final i0.Value<int> id;
final i0.Value<String> message;
final i0.Value<String?> details;
final i0.Value<i2.LogLevel> level;
final i0.Value<DateTime> createdAt;
final i0.Value<String?> context1;
final i0.Value<String?> context2;
const LoggerMessageEntityCompanion({
this.id = const i0.Value.absent(),
this.message = const i0.Value.absent(),
this.details = const i0.Value.absent(),
this.level = const i0.Value.absent(),
this.createdAt = const i0.Value.absent(),
this.context1 = const i0.Value.absent(),
this.context2 = const i0.Value.absent(),
});
LoggerMessageEntityCompanion.insert({
required int id,
required String message,
this.details = const i0.Value.absent(),
required i2.LogLevel level,
required DateTime createdAt,
this.context1 = const i0.Value.absent(),
this.context2 = const i0.Value.absent(),
}) : id = i0.Value(id),
message = i0.Value(message),
level = i0.Value(level),
createdAt = i0.Value(createdAt);
static i0.Insertable<i1.LoggerMessageEntityData> custom({
i0.Expression<int>? id,
i0.Expression<String>? message,
i0.Expression<String>? details,
i0.Expression<int>? level,
i0.Expression<DateTime>? createdAt,
i0.Expression<String>? context1,
i0.Expression<String>? context2,
}) {
return i0.RawValuesInsertable({
if (id != null) 'id': id,
if (message != null) 'message': message,
if (details != null) 'details': details,
if (level != null) 'level': level,
if (createdAt != null) 'created_at': createdAt,
if (context1 != null) 'context1': context1,
if (context2 != null) 'context2': context2,
});
}
i1.LoggerMessageEntityCompanion copyWith(
{i0.Value<int>? id,
i0.Value<String>? message,
i0.Value<String?>? details,
i0.Value<i2.LogLevel>? level,
i0.Value<DateTime>? createdAt,
i0.Value<String?>? context1,
i0.Value<String?>? context2}) {
return i1.LoggerMessageEntityCompanion(
id: id ?? this.id,
message: message ?? this.message,
details: details ?? this.details,
level: level ?? this.level,
createdAt: createdAt ?? this.createdAt,
context1: context1 ?? this.context1,
context2: context2 ?? this.context2,
);
}
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
if (id.present) {
map['id'] = i0.Variable<int>(id.value);
}
if (message.present) {
map['message'] = i0.Variable<String>(message.value);
}
if (details.present) {
map['details'] = i0.Variable<String>(details.value);
}
if (level.present) {
map['level'] = i0.Variable<int>(
i1.$LoggerMessageEntityTable.$converterlevel.toSql(level.value));
}
if (createdAt.present) {
map['created_at'] = i0.Variable<DateTime>(createdAt.value);
}
if (context1.present) {
map['context1'] = i0.Variable<String>(context1.value);
}
if (context2.present) {
map['context2'] = i0.Variable<String>(context2.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('LoggerMessageEntityCompanion(')
..write('id: $id, ')
..write('message: $message, ')
..write('details: $details, ')
..write('level: $level, ')
..write('createdAt: $createdAt, ')
..write('context1: $context1, ')
..write('context2: $context2')
..write(')'))
.toString();
}
}

View File

@@ -1,13 +1,13 @@
import 'package:isar/isar.dart';
import 'package:drift/drift.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
part 'store.entity.g.dart';
class StoreEntity extends Table with DriftDefaultsMixin {
const StoreEntity();
/// Internal class for `Store`, do not use elsewhere.
@Collection(inheritance: false)
class StoreValue {
final Id id;
final int? intValue;
final String? strValue;
IntColumn get id => integer()();
IntColumn get intValue => integer().nullable()();
TextColumn get strValue => text().nullable()();
const StoreValue(this.id, {this.intValue, this.strValue});
@override
Set<Column> get primaryKey => {id};
}

View File

@@ -0,0 +1,364 @@
// dart format width=80
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
as i1;
import 'package:immich_mobile/infrastructure/entities/store.entity.dart' as i2;
typedef $$StoreEntityTableCreateCompanionBuilder = i1.StoreEntityCompanion
Function({
required int id,
i0.Value<int?> intValue,
i0.Value<String?> strValue,
});
typedef $$StoreEntityTableUpdateCompanionBuilder = i1.StoreEntityCompanion
Function({
i0.Value<int> id,
i0.Value<int?> intValue,
i0.Value<String?> strValue,
});
class $$StoreEntityTableFilterComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$StoreEntityTable> {
$$StoreEntityTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnFilters<int> get id => $composableBuilder(
column: $table.id, builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<int> get intValue => $composableBuilder(
column: $table.intValue, builder: (column) => i0.ColumnFilters(column));
i0.ColumnFilters<String> get strValue => $composableBuilder(
column: $table.strValue, builder: (column) => i0.ColumnFilters(column));
}
class $$StoreEntityTableOrderingComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$StoreEntityTable> {
$$StoreEntityTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnOrderings<int> get id => $composableBuilder(
column: $table.id, builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<int> get intValue => $composableBuilder(
column: $table.intValue, builder: (column) => i0.ColumnOrderings(column));
i0.ColumnOrderings<String> get strValue => $composableBuilder(
column: $table.strValue, builder: (column) => i0.ColumnOrderings(column));
}
class $$StoreEntityTableAnnotationComposer
extends i0.Composer<i0.GeneratedDatabase, i1.$StoreEntityTable> {
$$StoreEntityTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.GeneratedColumn<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
i0.GeneratedColumn<int> get intValue =>
$composableBuilder(column: $table.intValue, builder: (column) => column);
i0.GeneratedColumn<String> get strValue =>
$composableBuilder(column: $table.strValue, builder: (column) => column);
}
class $$StoreEntityTableTableManager extends i0.RootTableManager<
i0.GeneratedDatabase,
i1.$StoreEntityTable,
i1.StoreEntityData,
i1.$$StoreEntityTableFilterComposer,
i1.$$StoreEntityTableOrderingComposer,
i1.$$StoreEntityTableAnnotationComposer,
$$StoreEntityTableCreateCompanionBuilder,
$$StoreEntityTableUpdateCompanionBuilder,
(
i1.StoreEntityData,
i0.BaseReferences<i0.GeneratedDatabase, i1.$StoreEntityTable,
i1.StoreEntityData>
),
i1.StoreEntityData,
i0.PrefetchHooks Function()> {
$$StoreEntityTableTableManager(
i0.GeneratedDatabase db, i1.$StoreEntityTable table)
: super(i0.TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
i1.$$StoreEntityTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
i1.$$StoreEntityTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
i1.$$StoreEntityTableAnnotationComposer($db: db, $table: table),
updateCompanionCallback: ({
i0.Value<int> id = const i0.Value.absent(),
i0.Value<int?> intValue = const i0.Value.absent(),
i0.Value<String?> strValue = const i0.Value.absent(),
}) =>
i1.StoreEntityCompanion(
id: id,
intValue: intValue,
strValue: strValue,
),
createCompanionCallback: ({
required int id,
i0.Value<int?> intValue = const i0.Value.absent(),
i0.Value<String?> strValue = const i0.Value.absent(),
}) =>
i1.StoreEntityCompanion.insert(
id: id,
intValue: intValue,
strValue: strValue,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
));
}
typedef $$StoreEntityTableProcessedTableManager = i0.ProcessedTableManager<
i0.GeneratedDatabase,
i1.$StoreEntityTable,
i1.StoreEntityData,
i1.$$StoreEntityTableFilterComposer,
i1.$$StoreEntityTableOrderingComposer,
i1.$$StoreEntityTableAnnotationComposer,
$$StoreEntityTableCreateCompanionBuilder,
$$StoreEntityTableUpdateCompanionBuilder,
(
i1.StoreEntityData,
i0.BaseReferences<i0.GeneratedDatabase, i1.$StoreEntityTable,
i1.StoreEntityData>
),
i1.StoreEntityData,
i0.PrefetchHooks Function()>;
class $StoreEntityTable extends i2.StoreEntity
with i0.TableInfo<$StoreEntityTable, i1.StoreEntityData> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
$StoreEntityTable(this.attachedDatabase, [this._alias]);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<int> id = i0.GeneratedColumn<int>(
'id', aliasedName, false,
type: i0.DriftSqlType.int, requiredDuringInsert: true);
static const i0.VerificationMeta _intValueMeta =
const i0.VerificationMeta('intValue');
@override
late final i0.GeneratedColumn<int> intValue = i0.GeneratedColumn<int>(
'int_value', aliasedName, true,
type: i0.DriftSqlType.int, requiredDuringInsert: false);
static const i0.VerificationMeta _strValueMeta =
const i0.VerificationMeta('strValue');
@override
late final i0.GeneratedColumn<String> strValue = i0.GeneratedColumn<String>(
'str_value', aliasedName, true,
type: i0.DriftSqlType.string, requiredDuringInsert: false);
@override
List<i0.GeneratedColumn> get $columns => [id, intValue, strValue];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'store_entity';
@override
i0.VerificationContext validateIntegrity(
i0.Insertable<i1.StoreEntityData> instance,
{bool isInserting = false}) {
final context = i0.VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
} else if (isInserting) {
context.missing(_idMeta);
}
if (data.containsKey('int_value')) {
context.handle(_intValueMeta,
intValue.isAcceptableOrUnknown(data['int_value']!, _intValueMeta));
}
if (data.containsKey('str_value')) {
context.handle(_strValueMeta,
strValue.isAcceptableOrUnknown(data['str_value']!, _strValueMeta));
}
return context;
}
@override
Set<i0.GeneratedColumn> get $primaryKey => {id};
@override
i1.StoreEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.StoreEntityData(
id: attachedDatabase.typeMapping
.read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!,
intValue: attachedDatabase.typeMapping
.read(i0.DriftSqlType.int, data['${effectivePrefix}int_value']),
strValue: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}str_value']),
);
}
@override
$StoreEntityTable createAlias(String alias) {
return $StoreEntityTable(attachedDatabase, alias);
}
@override
bool get withoutRowId => true;
@override
bool get isStrict => true;
}
class StoreEntityData extends i0.DataClass
implements i0.Insertable<i1.StoreEntityData> {
final int id;
final int? intValue;
final String? strValue;
const StoreEntityData({required this.id, this.intValue, this.strValue});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
map['id'] = i0.Variable<int>(id);
if (!nullToAbsent || intValue != null) {
map['int_value'] = i0.Variable<int>(intValue);
}
if (!nullToAbsent || strValue != null) {
map['str_value'] = i0.Variable<String>(strValue);
}
return map;
}
factory StoreEntityData.fromJson(Map<String, dynamic> json,
{i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return StoreEntityData(
id: serializer.fromJson<int>(json['id']),
intValue: serializer.fromJson<int?>(json['intValue']),
strValue: serializer.fromJson<String?>(json['strValue']),
);
}
@override
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'intValue': serializer.toJson<int?>(intValue),
'strValue': serializer.toJson<String?>(strValue),
};
}
i1.StoreEntityData copyWith(
{int? id,
i0.Value<int?> intValue = const i0.Value.absent(),
i0.Value<String?> strValue = const i0.Value.absent()}) =>
i1.StoreEntityData(
id: id ?? this.id,
intValue: intValue.present ? intValue.value : this.intValue,
strValue: strValue.present ? strValue.value : this.strValue,
);
StoreEntityData copyWithCompanion(i1.StoreEntityCompanion data) {
return StoreEntityData(
id: data.id.present ? data.id.value : this.id,
intValue: data.intValue.present ? data.intValue.value : this.intValue,
strValue: data.strValue.present ? data.strValue.value : this.strValue,
);
}
@override
String toString() {
return (StringBuffer('StoreEntityData(')
..write('id: $id, ')
..write('intValue: $intValue, ')
..write('strValue: $strValue')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, intValue, strValue);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.StoreEntityData &&
other.id == this.id &&
other.intValue == this.intValue &&
other.strValue == this.strValue);
}
class StoreEntityCompanion extends i0.UpdateCompanion<i1.StoreEntityData> {
final i0.Value<int> id;
final i0.Value<int?> intValue;
final i0.Value<String?> strValue;
const StoreEntityCompanion({
this.id = const i0.Value.absent(),
this.intValue = const i0.Value.absent(),
this.strValue = const i0.Value.absent(),
});
StoreEntityCompanion.insert({
required int id,
this.intValue = const i0.Value.absent(),
this.strValue = const i0.Value.absent(),
}) : id = i0.Value(id);
static i0.Insertable<i1.StoreEntityData> custom({
i0.Expression<int>? id,
i0.Expression<int>? intValue,
i0.Expression<String>? strValue,
}) {
return i0.RawValuesInsertable({
if (id != null) 'id': id,
if (intValue != null) 'int_value': intValue,
if (strValue != null) 'str_value': strValue,
});
}
i1.StoreEntityCompanion copyWith(
{i0.Value<int>? id,
i0.Value<int?>? intValue,
i0.Value<String?>? strValue}) {
return i1.StoreEntityCompanion(
id: id ?? this.id,
intValue: intValue ?? this.intValue,
strValue: strValue ?? this.strValue,
);
}
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
if (id.present) {
map['id'] = i0.Variable<int>(id.value);
}
if (intValue.present) {
map['int_value'] = i0.Variable<int>(intValue.value);
}
if (strValue.present) {
map['str_value'] = i0.Variable<String>(strValue.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('StoreEntityCompanion(')
..write('id: $id, ')
..write('intValue: $intValue, ')
..write('strValue: $strValue')
..write(')'))
.toString();
}
}

View File

@@ -7,11 +7,13 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
import 'package:isar/isar.dart';
@@ -46,6 +48,8 @@ class IsarDatabaseRepository implements IDatabaseRepository {
RemoteAlbumEntity,
RemoteAlbumAssetEntity,
RemoteAlbumUserEntity,
StoreEntity,
LoggerMessageEntity,
],
include: {
'package:immich_mobile/infrastructure/entities/merged_asset.drift',

View File

@@ -23,9 +23,13 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.
as i10;
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
as i11;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
as i12;
import 'package:drift/internal/modular.dart' as i13;
import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart'
as i13;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
as i14;
import 'package:drift/internal/modular.dart' as i15;
abstract class $Drift extends i0.GeneratedDatabase {
$Drift(i0.QueryExecutor e) : super(e);
@@ -51,8 +55,11 @@ abstract class $Drift extends i0.GeneratedDatabase {
i10.$RemoteAlbumAssetEntityTable(this);
late final i11.$RemoteAlbumUserEntityTable remoteAlbumUserEntity =
i11.$RemoteAlbumUserEntityTable(this);
i12.MergedAssetDrift get mergedAssetDrift => i13.ReadDatabaseContainer(this)
.accessor<i12.MergedAssetDrift>(i12.MergedAssetDrift.new);
late final i12.$StoreEntityTable storeEntity = i12.$StoreEntityTable(this);
late final i13.$LoggerMessageEntityTable loggerMessageEntity =
i13.$LoggerMessageEntityTable(this);
i14.MergedAssetDrift get mergedAssetDrift => i15.ReadDatabaseContainer(this)
.accessor<i14.MergedAssetDrift>(i14.MergedAssetDrift.new);
@override
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
@@ -71,7 +78,9 @@ abstract class $Drift extends i0.GeneratedDatabase {
remoteExifEntity,
remoteAlbumEntity,
remoteAlbumAssetEntity,
remoteAlbumUserEntity
remoteAlbumUserEntity,
storeEntity,
loggerMessageEntity
];
@override
i0.StreamQueryUpdateRules get streamUpdateRules =>
@@ -208,4 +217,8 @@ class $DriftManager {
_db, _db.remoteAlbumAssetEntity);
i11.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i11
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
i12.$$StoreEntityTableTableManager get storeEntity =>
i12.$$StoreEntityTableTableManager(_db, _db.storeEntity);
i13.$$LoggerMessageEntityTableTableManager get loggerMessageEntity =>
i13.$$LoggerMessageEntityTableTableManager(_db, _db.loggerMessageEntity);
}

View File

@@ -0,0 +1,145 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/drift_user.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
final driftStoreRepositoryProvider = Provider<DriftStoreRepository>(
(ref) => DriftStoreRepository(ref.watch(driftProvider)),
);
class DriftStoreRepository implements IStoreRepository {
final Drift _db;
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
DriftStoreRepository(this._db);
@override
Future<bool> deleteAll() async {
return await _db.transaction(() async {
await _db.delete(_db.storeEntity).go();
return true;
});
}
@override
Stream<StoreDto<Object>> watchAll() {
return (_db.select(_db.storeEntity)
..where((tbl) => tbl.id.isIn(validStoreKeys)))
.watch()
.asyncExpand(
(entities) => Stream.fromFutures(
entities.map((e) async => _toUpdateEvent(e)),
),
);
}
@override
Future<void> delete<T>(StoreKey<T> key) async {
return await _db.transaction(() async {
await (_db.delete(_db.storeEntity)..where((tbl) => tbl.id.equals(key.id)))
.go();
});
}
@override
Future<bool> insert<T>(StoreKey<T> key, T value) async {
return await _db.transaction(() async {
await _db
.into(_db.storeEntity)
.insertOnConflictUpdate(await _fromValue(key, value));
return true;
});
}
@override
Future<T?> tryGet<T>(StoreKey<T> key) async {
final entity = await (_db.select(_db.storeEntity)
..where((tbl) => tbl.id.equals(key.id)))
.getSingleOrNull();
if (entity == null) {
return null;
}
return await _toValue(key, entity);
}
@override
Future<bool> update<T>(StoreKey<T> key, T value) async {
return await _db.transaction(() async {
await _db
.into(_db.storeEntity)
.insertOnConflictUpdate(await _fromValue(key, value));
return true;
});
}
@override
Stream<T?> watch<T>(StoreKey<T> key) async* {
yield* (_db.select(_db.storeEntity)..where((tbl) => tbl.id.equals(key.id)))
.watchSingleOrNull()
.asyncMap((e) async => e == null ? null : await _toValue(key, e));
}
Future<StoreDto<Object>> _toUpdateEvent(StoreEntityData entity) async {
final key = StoreKey.values.firstWhere((e) => e.id == entity.id)
as StoreKey<Object>;
final value = await _toValue(key, entity);
return StoreDto(key, value);
}
Future<T?> _toValue<T>(StoreKey<T> key, StoreEntityData entity) async =>
switch (key.type) {
const (int) => entity.intValue,
const (String) => entity.strValue,
const (bool) => entity.intValue == 1,
const (DateTime) => entity.intValue == null
? null
: DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
const (UserDto) => entity.strValue == null
? null
: await DriftUserRepository(_db).getByUserId(entity.strValue!),
_ => null,
} as T?;
Future<StoreEntityData> _fromValue<T>(StoreKey<T> key, T value) async {
final (int? intValue, String? strValue) = switch (key.type) {
const (int) => (value as int, null),
const (String) => (null, value as String),
const (bool) => ((value as bool) ? 1 : 0, null),
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
const (UserDto) => (
null,
(await DriftUserRepository(_db).update(value as UserDto)).id,
),
_ => throw UnsupportedError(
"Unsupported primitive type: ${key.type} for key: ${key.name}",
),
};
return StoreEntityData(
id: key.id,
intValue: intValue,
strValue: strValue,
);
}
@override
Future<List<StoreDto<Object>>> getAll() async {
final entities = await (_db.select(_db.storeEntity)
..where((tbl) => tbl.id.isIn(validStoreKeys)))
.get();
return Future.wait(entities.map((e) => _toUpdateEvent(e)).toList());
}
}
abstract class IStoreRepository {
Future<bool> deleteAll();
Stream<StoreDto<Object>> watchAll();
Future<void> delete<T>(StoreKey<T> key);
Future<bool> insert<T>(StoreKey<T> key, T value);
Future<T?> tryGet<T>(StoreKey<T> key);
Future<bool> update<T>(StoreKey<T> key, T value);
Stream<T?> watch<T>(StoreKey<T> key);
Future<List<StoreDto<Object>>> getAll();
}

View File

@@ -0,0 +1,117 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
class DriftUserRepository {
final Drift _db;
const DriftUserRepository(this._db);
Future<void> delete(List<String> ids) async {
await _db.transaction(() async {
await (_db.delete(_db.userEntity)..where((tbl) => tbl.id.isIn(ids))).go();
});
}
Future<void> deleteAll() async {
await _db.transaction(() async {
await _db.delete(_db.userEntity).go();
});
}
Future<List<UserDto>> getAll({SortUserBy? sortBy}) async {
var query = _db.select(_db.userEntity);
if (sortBy != null) {
switch (sortBy) {
case SortUserBy.id:
query = query..orderBy([(u) => OrderingTerm.asc(u.id)]);
}
}
final users = await query.get();
return users.map((u) => _toDto(u)).toList();
}
Future<UserDto?> getByUserId(String id) async {
final user = await (_db.select(_db.userEntity)
..where((tbl) => tbl.id.equals(id)))
.getSingleOrNull();
return user != null ? _toDto(user) : null;
}
Future<List<UserDto?>> getByUserIds(List<String> ids) async {
final users = await (_db.select(_db.userEntity)
..where((tbl) => tbl.id.isIn(ids)))
.get();
// Create a map for quick lookup
final userMap = {for (var user in users) user.id: _toDto(user)};
// Return results in the same order as input ids
return ids.map((id) => userMap[id]).toList();
}
Future<bool> insert(UserDto user) async {
await _db.transaction(() async {
await _db.into(_db.userEntity).insertOnConflictUpdate(_fromDto(user));
});
return true;
}
Future<UserDto> update(UserDto user) async {
await _db.transaction(() async {
await _db.into(_db.userEntity).insertOnConflictUpdate(_fromDto(user));
});
return user;
}
Future<bool> updateAll(List<UserDto> users) async {
await _db.transaction(() async {
await _db.batch((batch) {
for (final user in users) {
batch.insert(_db.userEntity, _fromDto(user),
mode: InsertMode.insertOrReplace);
}
});
});
return true;
}
UserDto _toDto(UserEntityData entity) {
return UserDto(
id: entity.id,
updatedAt: entity.updatedAt,
email: entity.email,
name: entity.name,
isAdmin: entity.isAdmin,
profileImagePath: entity.profileImagePath ?? '',
// Note: These fields are not in the current UserEntity table but are in UserDto
// You may need to add them to the table or provide defaults
isPartnerSharedBy: false,
isPartnerSharedWith: false,
avatarColor: AvatarColor.primary,
memoryEnabled: true,
inTimeline: false,
quotaUsageInBytes: entity.quotaUsageInBytes,
quotaSizeInBytes: entity.quotaSizeInBytes ?? 0,
);
}
UserEntityCompanion _fromDto(UserDto dto) {
return UserEntityCompanion(
id: Value(dto.id),
name: Value(dto.name),
isAdmin: Value(dto.isAdmin),
email: Value(dto.email),
profileImagePath: Value.absentIfNull(
dto.profileImagePath?.isEmpty == true ? null : dto.profileImagePath),
updatedAt: Value(dto.updatedAt),
quotaSizeInBytes: Value.absentIfNull(
dto.quotaSizeInBytes == 0 ? null : dto.quotaSizeInBytes),
quotaUsageInBytes: Value(dto.quotaUsageInBytes),
);
}
}

View File

@@ -1,46 +1,99 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:isar/isar.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
class IsarLogRepository extends IsarDatabaseRepository {
final Isar _db;
const IsarLogRepository(super.db) : _db = db;
final driftLogRepositoryProvider = Provider<LogRepository>(
(ref) => LogRepository(ref.watch(driftProvider)),
);
class LogRepository {
final Drift _db;
const LogRepository(this._db);
Future<bool> deleteAll() async {
await transaction(() async => await _db.loggerMessages.clear());
await _db.transaction(() async {
await _db.delete(_db.loggerMessageEntity).go();
});
return true;
}
Future<List<LogMessage>> getAll() async {
final logs =
await _db.loggerMessages.where().sortByCreatedAtDesc().findAll();
return logs.map((l) => l.toDto()).toList();
final query = _db.select(_db.loggerMessageEntity)
..orderBy([(t) => OrderingTerm.desc(t.createdAt)]);
final results = await query.get();
return results
.map(
(row) => LogMessage(
message: row.message,
level: row.level,
createdAt: row.createdAt,
logger: row.context1,
error: row.details,
stack: row.context2,
),
)
.toList();
}
Future<bool> insert(LogMessage log) async {
final logEntity = LoggerMessage.fromDto(log);
await transaction(() async {
await _db.loggerMessages.put(logEntity);
await _db.transaction(() async {
await _db.into(_db.loggerMessageEntity).insert(
LoggerMessageEntityCompanion.insert(
id: 0, // Will be auto-incremented by the database
message: log.message,
details: Value(log.error),
level: log.level,
createdAt: log.createdAt,
context1: Value(log.logger),
context2: Value(log.stack),
),
);
});
return true;
}
Future<bool> insertAll(Iterable<LogMessage> logs) async {
await transaction(() async {
final logEntities =
logs.map((log) => LoggerMessage.fromDto(log)).toList();
await _db.loggerMessages.putAll(logEntities);
await _db.transaction(() async {
for (final log in logs) {
await _db.into(_db.loggerMessageEntity).insert(
LoggerMessageEntityCompanion.insert(
id: 0, // Will be auto-incremented by the database
message: log.message,
details: Value(log.error),
level: log.level,
createdAt: log.createdAt,
context1: Value(log.logger),
context2: Value(log.stack),
),
);
}
});
return true;
}
Future<void> truncate({int limit = 250}) async {
await transaction(() async {
final count = await _db.loggerMessages.count();
await _db.transaction(() async {
final countQuery = _db.selectOnly(_db.loggerMessageEntity)
..addColumns([_db.loggerMessageEntity.id.count()]);
final countResult = await countQuery.getSingle();
final count = countResult.read(_db.loggerMessageEntity.id.count()) ?? 0;
if (count <= limit) return;
final toRemove = count - limit;
await _db.loggerMessages.where().limit(toRemove).deleteAll();
final oldestIds = await (_db.select(_db.loggerMessageEntity)
..orderBy([(t) => OrderingTerm.asc(t.createdAt)])
..limit(toRemove))
.get();
final idsToDelete = oldestIds.map((row) => row.id).toList();
await (_db.delete(_db.loggerMessageEntity)
..where((tbl) => tbl.id.isIn(idsToDelete)))
.go();
});
}
}

View File

@@ -1,16 +1,19 @@
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/isar_store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/drift_store.repository.dart';
import 'package:isar/isar.dart';
class IsarStoreRepository extends IsarDatabaseRepository {
class IsarStoreRepository extends IsarDatabaseRepository
implements IStoreRepository {
final Isar _db;
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
IsarStoreRepository(super.db) : _db = db;
@override
Future<bool> deleteAll() async {
return await transaction(() async {
await _db.storeValues.clear();
@@ -18,6 +21,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
});
}
@override
Stream<StoreDto<Object>> watchAll() {
return _db.storeValues
.filter()
@@ -30,10 +34,12 @@ class IsarStoreRepository extends IsarDatabaseRepository {
);
}
@override
Future<void> delete<T>(StoreKey<T> key) async {
return await transaction(() async => await _db.storeValues.delete(key.id));
}
@override
Future<bool> insert<T>(StoreKey<T> key, T value) async {
return await transaction(() async {
await _db.storeValues.put(await _fromValue(key, value));
@@ -41,6 +47,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
});
}
@override
Future<T?> tryGet<T>(StoreKey<T> key) async {
final entity = (await _db.storeValues.get(key.id));
if (entity == null) {
@@ -49,6 +56,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
return await _toValue(key, entity);
}
@override
Future<bool> update<T>(StoreKey<T> key, T value) async {
return await transaction(() async {
await _db.storeValues.put(await _fromValue(key, value));
@@ -56,6 +64,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
});
}
@override
Stream<T?> watch<T>(StoreKey<T> key) async* {
yield* _db.storeValues
.watchObject(key.id, fireImmediately: true)
@@ -100,6 +109,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
return StoreValue(key.id, intValue: intValue, strValue: strValue);
}
@override
Future<List<StoreDto<Object>>> getAll() async {
final entities = await _db.storeValues
.filter()

View File

@@ -1,12 +1,11 @@
import 'dart:async';
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
// ignore: import_rule_isar
import 'package:isar/isar.dart';
const kDevLoggerTag = 'DEV';
@@ -14,28 +13,38 @@ abstract final class DLog {
const DLog();
static Stream<List<LogMessage>> watchLog() {
final db = Isar.getInstance();
if (db == null) {
return const Stream.empty();
}
final db = Drift();
return db.loggerMessages
.filter()
.context1EqualTo(kDevLoggerTag)
.sortByCreatedAtDesc()
.watch(fireImmediately: true)
.map((logs) => logs.map((log) => log.toDto()).toList());
final query = db.select(db.loggerMessageEntity)
..where((tbl) => tbl.context1.equals(kDevLoggerTag))
..orderBy([(t) => OrderingTerm.desc(t.createdAt)]);
return query.watch().map(
(rows) => rows
.map(
(row) => LogMessage(
message: row.message,
level: row.level,
createdAt: row.createdAt,
logger: row.context1,
error: row.details,
stack: row.context2,
),
)
.toList(),
);
}
static void clearLog() {
final db = Isar.getInstance();
if (db == null) {
return;
}
final db = Drift();
db.writeTxnSync(() {
db.loggerMessages.filter().context1EqualTo(kDevLoggerTag).deleteAllSync();
});
unawaited(
db.transaction(() async {
await (db.delete(db.loggerMessageEntity)
..where((tbl) => tbl.context1.equals(kDevLoggerTag)))
.go();
}),
);
}
static void log(String message, [Object? error, StackTrace? stackTrace]) {
@@ -49,10 +58,7 @@ abstract final class DLog {
debugPrint('StackTrace: $stackTrace');
}
final isar = Isar.getInstance();
if (isar == null) {
return;
}
final db = Drift();
final record = LogMessage(
message: message,
@@ -63,6 +69,6 @@ abstract final class DLog {
stack: stackTrace?.toString(),
);
unawaited(IsarLogRepository(isar).insert(record));
unawaited(LogRepository(db).insert(record));
}
}

View File

@@ -5,8 +5,11 @@ import 'package:drift/drift.dart' hide Column;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/drift_store.repository.dart';
import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
@@ -14,6 +17,32 @@ import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/routing/router.dart';
final _features = [
_Feature(
name: 'test',
icon: Icons.abc,
onTap: (_, ref) {
final UserDto value = UserDto(
id: "1234",
email: "alex@email.com",
name: "alex",
isAdmin: true,
updatedAt: DateTime.now(),
);
ref
.read(driftStoreRepositoryProvider)
.insert(StoreKey.serverUrl, "https://example.com");
final readback =
ref.read(driftStoreRepositoryProvider).tryGet(StoreKey.serverUrl);
readback.then((value) {
print("Read back: $value");
});
return Future.value();
},
),
_Feature(
name: 'Sync Local',
icon: Icons.photo_album_rounded,

View File

@@ -14,7 +14,7 @@ part of 'router.dart';
/// [ActivitiesPage]
class ActivitiesRoute extends PageRouteInfo<void> {
const ActivitiesRoute({List<PageRouteInfo>? children})
: super(ActivitiesRoute.name, initialChildren: children);
: super(ActivitiesRoute.name, initialChildren: children);
static const String name = 'ActivitiesRoute';
@@ -35,13 +35,13 @@ class AlbumAdditionalSharedUserSelectionRoute
required Album album,
List<PageRouteInfo>? children,
}) : super(
AlbumAdditionalSharedUserSelectionRoute.name,
args: AlbumAdditionalSharedUserSelectionRouteArgs(
key: key,
album: album,
),
initialChildren: children,
);
AlbumAdditionalSharedUserSelectionRoute.name,
args: AlbumAdditionalSharedUserSelectionRouteArgs(
key: key,
album: album,
),
initialChildren: children,
);
static const String name = 'AlbumAdditionalSharedUserSelectionRoute';
@@ -83,14 +83,14 @@ class AlbumAssetSelectionRoute
bool canDeselect = false,
List<PageRouteInfo>? children,
}) : super(
AlbumAssetSelectionRoute.name,
args: AlbumAssetSelectionRouteArgs(
key: key,
existingAssets: existingAssets,
canDeselect: canDeselect,
),
initialChildren: children,
);
AlbumAssetSelectionRoute.name,
args: AlbumAssetSelectionRouteArgs(
key: key,
existingAssets: existingAssets,
canDeselect: canDeselect,
),
initialChildren: children,
);
static const String name = 'AlbumAssetSelectionRoute';
@@ -130,7 +130,7 @@ class AlbumAssetSelectionRouteArgs {
/// [AlbumOptionsPage]
class AlbumOptionsRoute extends PageRouteInfo<void> {
const AlbumOptionsRoute({List<PageRouteInfo>? children})
: super(AlbumOptionsRoute.name, initialChildren: children);
: super(AlbumOptionsRoute.name, initialChildren: children);
static const String name = 'AlbumOptionsRoute';
@@ -150,10 +150,10 @@ class AlbumPreviewRoute extends PageRouteInfo<AlbumPreviewRouteArgs> {
required Album album,
List<PageRouteInfo>? children,
}) : super(
AlbumPreviewRoute.name,
args: AlbumPreviewRouteArgs(key: key, album: album),
initialChildren: children,
);
AlbumPreviewRoute.name,
args: AlbumPreviewRouteArgs(key: key, album: album),
initialChildren: children,
);
static const String name = 'AlbumPreviewRoute';
@@ -188,10 +188,10 @@ class AlbumSharedUserSelectionRoute
required Set<Asset> assets,
List<PageRouteInfo>? children,
}) : super(
AlbumSharedUserSelectionRoute.name,
args: AlbumSharedUserSelectionRouteArgs(key: key, assets: assets),
initialChildren: children,
);
AlbumSharedUserSelectionRoute.name,
args: AlbumSharedUserSelectionRouteArgs(key: key, assets: assets),
initialChildren: children,
);
static const String name = 'AlbumSharedUserSelectionRoute';
@@ -225,10 +225,10 @@ class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
required int albumId,
List<PageRouteInfo>? children,
}) : super(
AlbumViewerRoute.name,
args: AlbumViewerRouteArgs(key: key, albumId: albumId),
initialChildren: children,
);
AlbumViewerRoute.name,
args: AlbumViewerRouteArgs(key: key, albumId: albumId),
initialChildren: children,
);
static const String name = 'AlbumViewerRoute';
@@ -258,7 +258,7 @@ class AlbumViewerRouteArgs {
/// [AlbumsPage]
class AlbumsRoute extends PageRouteInfo<void> {
const AlbumsRoute({List<PageRouteInfo>? children})
: super(AlbumsRoute.name, initialChildren: children);
: super(AlbumsRoute.name, initialChildren: children);
static const String name = 'AlbumsRoute';
@@ -274,7 +274,7 @@ class AlbumsRoute extends PageRouteInfo<void> {
/// [AllMotionPhotosPage]
class AllMotionPhotosRoute extends PageRouteInfo<void> {
const AllMotionPhotosRoute({List<PageRouteInfo>? children})
: super(AllMotionPhotosRoute.name, initialChildren: children);
: super(AllMotionPhotosRoute.name, initialChildren: children);
static const String name = 'AllMotionPhotosRoute';
@@ -290,7 +290,7 @@ class AllMotionPhotosRoute extends PageRouteInfo<void> {
/// [AllPeoplePage]
class AllPeopleRoute extends PageRouteInfo<void> {
const AllPeopleRoute({List<PageRouteInfo>? children})
: super(AllPeopleRoute.name, initialChildren: children);
: super(AllPeopleRoute.name, initialChildren: children);
static const String name = 'AllPeopleRoute';
@@ -306,7 +306,7 @@ class AllPeopleRoute extends PageRouteInfo<void> {
/// [AllPlacesPage]
class AllPlacesRoute extends PageRouteInfo<void> {
const AllPlacesRoute({List<PageRouteInfo>? children})
: super(AllPlacesRoute.name, initialChildren: children);
: super(AllPlacesRoute.name, initialChildren: children);
static const String name = 'AllPlacesRoute';
@@ -322,7 +322,7 @@ class AllPlacesRoute extends PageRouteInfo<void> {
/// [AllVideosPage]
class AllVideosRoute extends PageRouteInfo<void> {
const AllVideosRoute({List<PageRouteInfo>? children})
: super(AllVideosRoute.name, initialChildren: children);
: super(AllVideosRoute.name, initialChildren: children);
static const String name = 'AllVideosRoute';
@@ -342,10 +342,10 @@ class AppLogDetailRoute extends PageRouteInfo<AppLogDetailRouteArgs> {
required LogMessage logMessage,
List<PageRouteInfo>? children,
}) : super(
AppLogDetailRoute.name,
args: AppLogDetailRouteArgs(key: key, logMessage: logMessage),
initialChildren: children,
);
AppLogDetailRoute.name,
args: AppLogDetailRouteArgs(key: key, logMessage: logMessage),
initialChildren: children,
);
static const String name = 'AppLogDetailRoute';
@@ -375,7 +375,7 @@ class AppLogDetailRouteArgs {
/// [AppLogPage]
class AppLogRoute extends PageRouteInfo<void> {
const AppLogRoute({List<PageRouteInfo>? children})
: super(AppLogRoute.name, initialChildren: children);
: super(AppLogRoute.name, initialChildren: children);
static const String name = 'AppLogRoute';
@@ -391,7 +391,7 @@ class AppLogRoute extends PageRouteInfo<void> {
/// [ArchivePage]
class ArchiveRoute extends PageRouteInfo<void> {
const ArchiveRoute({List<PageRouteInfo>? children})
: super(ArchiveRoute.name, initialChildren: children);
: super(ArchiveRoute.name, initialChildren: children);
static const String name = 'ArchiveRoute';
@@ -407,7 +407,7 @@ class ArchiveRoute extends PageRouteInfo<void> {
/// [BackupAlbumSelectionPage]
class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
const BackupAlbumSelectionRoute({List<PageRouteInfo>? children})
: super(BackupAlbumSelectionRoute.name, initialChildren: children);
: super(BackupAlbumSelectionRoute.name, initialChildren: children);
static const String name = 'BackupAlbumSelectionRoute';
@@ -423,7 +423,7 @@ class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
/// [BackupControllerPage]
class BackupControllerRoute extends PageRouteInfo<void> {
const BackupControllerRoute({List<PageRouteInfo>? children})
: super(BackupControllerRoute.name, initialChildren: children);
: super(BackupControllerRoute.name, initialChildren: children);
static const String name = 'BackupControllerRoute';
@@ -439,7 +439,7 @@ class BackupControllerRoute extends PageRouteInfo<void> {
/// [BackupOptionsPage]
class BackupOptionsRoute extends PageRouteInfo<void> {
const BackupOptionsRoute({List<PageRouteInfo>? children})
: super(BackupOptionsRoute.name, initialChildren: children);
: super(BackupOptionsRoute.name, initialChildren: children);
static const String name = 'BackupOptionsRoute';
@@ -455,7 +455,7 @@ class BackupOptionsRoute extends PageRouteInfo<void> {
/// [ChangePasswordPage]
class ChangePasswordRoute extends PageRouteInfo<void> {
const ChangePasswordRoute({List<PageRouteInfo>? children})
: super(ChangePasswordRoute.name, initialChildren: children);
: super(ChangePasswordRoute.name, initialChildren: children);
static const String name = 'ChangePasswordRoute';
@@ -475,10 +475,10 @@ class CreateAlbumRoute extends PageRouteInfo<CreateAlbumRouteArgs> {
List<Asset>? assets,
List<PageRouteInfo>? children,
}) : super(
CreateAlbumRoute.name,
args: CreateAlbumRouteArgs(key: key, assets: assets),
initialChildren: children,
);
CreateAlbumRoute.name,
args: CreateAlbumRouteArgs(key: key, assets: assets),
initialChildren: children,
);
static const String name = 'CreateAlbumRoute';
@@ -515,10 +515,10 @@ class CropImageRoute extends PageRouteInfo<CropImageRouteArgs> {
required Asset asset,
List<PageRouteInfo>? children,
}) : super(
CropImageRoute.name,
args: CropImageRouteArgs(key: key, image: image, asset: asset),
initialChildren: children,
);
CropImageRoute.name,
args: CropImageRouteArgs(key: key, image: image, asset: asset),
initialChildren: children,
);
static const String name = 'CropImageRoute';
@@ -560,15 +560,15 @@ class EditImageRoute extends PageRouteInfo<EditImageRouteArgs> {
required bool isEdited,
List<PageRouteInfo>? children,
}) : super(
EditImageRoute.name,
args: EditImageRouteArgs(
key: key,
asset: asset,
image: image,
isEdited: isEdited,
),
initialChildren: children,
);
EditImageRoute.name,
args: EditImageRouteArgs(
key: key,
asset: asset,
image: image,
isEdited: isEdited,
),
initialChildren: children,
);
static const String name = 'EditImageRoute';
@@ -612,7 +612,7 @@ class EditImageRouteArgs {
/// [FailedBackupStatusPage]
class FailedBackupStatusRoute extends PageRouteInfo<void> {
const FailedBackupStatusRoute({List<PageRouteInfo>? children})
: super(FailedBackupStatusRoute.name, initialChildren: children);
: super(FailedBackupStatusRoute.name, initialChildren: children);
static const String name = 'FailedBackupStatusRoute';
@@ -628,7 +628,7 @@ class FailedBackupStatusRoute extends PageRouteInfo<void> {
/// [FavoritesPage]
class FavoritesRoute extends PageRouteInfo<void> {
const FavoritesRoute({List<PageRouteInfo>? children})
: super(FavoritesRoute.name, initialChildren: children);
: super(FavoritesRoute.name, initialChildren: children);
static const String name = 'FavoritesRoute';
@@ -644,7 +644,7 @@ class FavoritesRoute extends PageRouteInfo<void> {
/// [FeatInDevPage]
class FeatInDevRoute extends PageRouteInfo<void> {
const FeatInDevRoute({List<PageRouteInfo>? children})
: super(FeatInDevRoute.name, initialChildren: children);
: super(FeatInDevRoute.name, initialChildren: children);
static const String name = 'FeatInDevRoute';
@@ -665,10 +665,10 @@ class FilterImageRoute extends PageRouteInfo<FilterImageRouteArgs> {
required Asset asset,
List<PageRouteInfo>? children,
}) : super(
FilterImageRoute.name,
args: FilterImageRouteArgs(key: key, image: image, asset: asset),
initialChildren: children,
);
FilterImageRoute.name,
args: FilterImageRouteArgs(key: key, image: image, asset: asset),
initialChildren: children,
);
static const String name = 'FilterImageRoute';
@@ -712,10 +712,10 @@ class FolderRoute extends PageRouteInfo<FolderRouteArgs> {
RecursiveFolder? folder,
List<PageRouteInfo>? children,
}) : super(
FolderRoute.name,
args: FolderRouteArgs(key: key, folder: folder),
initialChildren: children,
);
FolderRoute.name,
args: FolderRouteArgs(key: key, folder: folder),
initialChildren: children,
);
static const String name = 'FolderRoute';
@@ -754,16 +754,16 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
bool showStack = false,
List<PageRouteInfo>? children,
}) : super(
GalleryViewerRoute.name,
args: GalleryViewerRouteArgs(
key: key,
renderList: renderList,
initialIndex: initialIndex,
heroOffset: heroOffset,
showStack: showStack,
),
initialChildren: children,
);
GalleryViewerRoute.name,
args: GalleryViewerRouteArgs(
key: key,
renderList: renderList,
initialIndex: initialIndex,
heroOffset: heroOffset,
showStack: showStack,
),
initialChildren: children,
);
static const String name = 'GalleryViewerRoute';
@@ -811,7 +811,7 @@ class GalleryViewerRouteArgs {
/// [HeaderSettingsPage]
class HeaderSettingsRoute extends PageRouteInfo<void> {
const HeaderSettingsRoute({List<PageRouteInfo>? children})
: super(HeaderSettingsRoute.name, initialChildren: children);
: super(HeaderSettingsRoute.name, initialChildren: children);
static const String name = 'HeaderSettingsRoute';
@@ -827,7 +827,7 @@ class HeaderSettingsRoute extends PageRouteInfo<void> {
/// [LibraryPage]
class LibraryRoute extends PageRouteInfo<void> {
const LibraryRoute({List<PageRouteInfo>? children})
: super(LibraryRoute.name, initialChildren: children);
: super(LibraryRoute.name, initialChildren: children);
static const String name = 'LibraryRoute';
@@ -843,7 +843,7 @@ class LibraryRoute extends PageRouteInfo<void> {
/// [LocalAlbumsPage]
class LocalAlbumsRoute extends PageRouteInfo<void> {
const LocalAlbumsRoute({List<PageRouteInfo>? children})
: super(LocalAlbumsRoute.name, initialChildren: children);
: super(LocalAlbumsRoute.name, initialChildren: children);
static const String name = 'LocalAlbumsRoute';
@@ -859,7 +859,7 @@ class LocalAlbumsRoute extends PageRouteInfo<void> {
/// [LocalMediaSummaryPage]
class LocalMediaSummaryRoute extends PageRouteInfo<void> {
const LocalMediaSummaryRoute({List<PageRouteInfo>? children})
: super(LocalMediaSummaryRoute.name, initialChildren: children);
: super(LocalMediaSummaryRoute.name, initialChildren: children);
static const String name = 'LocalMediaSummaryRoute';
@@ -879,10 +879,10 @@ class LocalTimelineRoute extends PageRouteInfo<LocalTimelineRouteArgs> {
required String albumId,
List<PageRouteInfo>? children,
}) : super(
LocalTimelineRoute.name,
args: LocalTimelineRouteArgs(key: key, albumId: albumId),
initialChildren: children,
);
LocalTimelineRoute.name,
args: LocalTimelineRouteArgs(key: key, albumId: albumId),
initialChildren: children,
);
static const String name = 'LocalTimelineRoute';
@@ -912,7 +912,7 @@ class LocalTimelineRouteArgs {
/// [LockedPage]
class LockedRoute extends PageRouteInfo<void> {
const LockedRoute({List<PageRouteInfo>? children})
: super(LockedRoute.name, initialChildren: children);
: super(LockedRoute.name, initialChildren: children);
static const String name = 'LockedRoute';
@@ -928,7 +928,7 @@ class LockedRoute extends PageRouteInfo<void> {
/// [LoginPage]
class LoginRoute extends PageRouteInfo<void> {
const LoginRoute({List<PageRouteInfo>? children})
: super(LoginRoute.name, initialChildren: children);
: super(LoginRoute.name, initialChildren: children);
static const String name = 'LoginRoute';
@@ -944,7 +944,7 @@ class LoginRoute extends PageRouteInfo<void> {
/// [MainTimelinePage]
class MainTimelineRoute extends PageRouteInfo<void> {
const MainTimelineRoute({List<PageRouteInfo>? children})
: super(MainTimelineRoute.name, initialChildren: children);
: super(MainTimelineRoute.name, initialChildren: children);
static const String name = 'MainTimelineRoute';
@@ -964,13 +964,13 @@ class MapLocationPickerRoute extends PageRouteInfo<MapLocationPickerRouteArgs> {
LatLng initialLatLng = const LatLng(0, 0),
List<PageRouteInfo>? children,
}) : super(
MapLocationPickerRoute.name,
args: MapLocationPickerRouteArgs(
key: key,
initialLatLng: initialLatLng,
),
initialChildren: children,
);
MapLocationPickerRoute.name,
args: MapLocationPickerRouteArgs(
key: key,
initialLatLng: initialLatLng,
),
initialChildren: children,
);
static const String name = 'MapLocationPickerRoute';
@@ -1008,11 +1008,11 @@ class MapLocationPickerRouteArgs {
/// [MapPage]
class MapRoute extends PageRouteInfo<MapRouteArgs> {
MapRoute({Key? key, LatLng? initialLocation, List<PageRouteInfo>? children})
: super(
MapRoute.name,
args: MapRouteArgs(key: key, initialLocation: initialLocation),
initialChildren: children,
);
: super(
MapRoute.name,
args: MapRouteArgs(key: key, initialLocation: initialLocation),
initialChildren: children,
);
static const String name = 'MapRoute';
@@ -1049,14 +1049,14 @@ class MemoryRoute extends PageRouteInfo<MemoryRouteArgs> {
Key? key,
List<PageRouteInfo>? children,
}) : super(
MemoryRoute.name,
args: MemoryRouteArgs(
memories: memories,
memoryIndex: memoryIndex,
key: key,
),
initialChildren: children,
);
MemoryRoute.name,
args: MemoryRouteArgs(
memories: memories,
memoryIndex: memoryIndex,
key: key,
),
initialChildren: children,
);
static const String name = 'MemoryRoute';
@@ -1103,16 +1103,16 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
int playbackDelayFactor = 1,
List<PageRouteInfo>? children,
}) : super(
NativeVideoViewerRoute.name,
args: NativeVideoViewerRouteArgs(
key: key,
asset: asset,
image: image,
showControls: showControls,
playbackDelayFactor: playbackDelayFactor,
),
initialChildren: children,
);
NativeVideoViewerRoute.name,
args: NativeVideoViewerRouteArgs(
key: key,
asset: asset,
image: image,
showControls: showControls,
playbackDelayFactor: playbackDelayFactor,
),
initialChildren: children,
);
static const String name = 'NativeVideoViewerRoute';
@@ -1164,10 +1164,10 @@ class PartnerDetailRoute extends PageRouteInfo<PartnerDetailRouteArgs> {
required UserDto partner,
List<PageRouteInfo>? children,
}) : super(
PartnerDetailRoute.name,
args: PartnerDetailRouteArgs(key: key, partner: partner),
initialChildren: children,
);
PartnerDetailRoute.name,
args: PartnerDetailRouteArgs(key: key, partner: partner),
initialChildren: children,
);
static const String name = 'PartnerDetailRoute';
@@ -1197,7 +1197,7 @@ class PartnerDetailRouteArgs {
/// [PartnerPage]
class PartnerRoute extends PageRouteInfo<void> {
const PartnerRoute({List<PageRouteInfo>? children})
: super(PartnerRoute.name, initialChildren: children);
: super(PartnerRoute.name, initialChildren: children);
static const String name = 'PartnerRoute';
@@ -1213,7 +1213,7 @@ class PartnerRoute extends PageRouteInfo<void> {
/// [PeopleCollectionPage]
class PeopleCollectionRoute extends PageRouteInfo<void> {
const PeopleCollectionRoute({List<PageRouteInfo>? children})
: super(PeopleCollectionRoute.name, initialChildren: children);
: super(PeopleCollectionRoute.name, initialChildren: children);
static const String name = 'PeopleCollectionRoute';
@@ -1229,7 +1229,7 @@ class PeopleCollectionRoute extends PageRouteInfo<void> {
/// [PermissionOnboardingPage]
class PermissionOnboardingRoute extends PageRouteInfo<void> {
const PermissionOnboardingRoute({List<PageRouteInfo>? children})
: super(PermissionOnboardingRoute.name, initialChildren: children);
: super(PermissionOnboardingRoute.name, initialChildren: children);
static const String name = 'PermissionOnboardingRoute';
@@ -1250,14 +1250,14 @@ class PersonResultRoute extends PageRouteInfo<PersonResultRouteArgs> {
required String personName,
List<PageRouteInfo>? children,
}) : super(
PersonResultRoute.name,
args: PersonResultRouteArgs(
key: key,
personId: personId,
personName: personName,
),
initialChildren: children,
);
PersonResultRoute.name,
args: PersonResultRouteArgs(
key: key,
personId: personId,
personName: personName,
),
initialChildren: children,
);
static const String name = 'PersonResultRoute';
@@ -1297,7 +1297,7 @@ class PersonResultRouteArgs {
/// [PhotosPage]
class PhotosRoute extends PageRouteInfo<void> {
const PhotosRoute({List<PageRouteInfo>? children})
: super(PhotosRoute.name, initialChildren: children);
: super(PhotosRoute.name, initialChildren: children);
static const String name = 'PhotosRoute';
@@ -1317,10 +1317,10 @@ class PinAuthRoute extends PageRouteInfo<PinAuthRouteArgs> {
bool createPinCode = false,
List<PageRouteInfo>? children,
}) : super(
PinAuthRoute.name,
args: PinAuthRouteArgs(key: key, createPinCode: createPinCode),
initialChildren: children,
);
PinAuthRoute.name,
args: PinAuthRouteArgs(key: key, createPinCode: createPinCode),
initialChildren: children,
);
static const String name = 'PinAuthRoute';
@@ -1356,13 +1356,13 @@ class PlacesCollectionRoute extends PageRouteInfo<PlacesCollectionRouteArgs> {
LatLng? currentLocation,
List<PageRouteInfo>? children,
}) : super(
PlacesCollectionRoute.name,
args: PlacesCollectionRouteArgs(
key: key,
currentLocation: currentLocation,
),
initialChildren: children,
);
PlacesCollectionRoute.name,
args: PlacesCollectionRouteArgs(
key: key,
currentLocation: currentLocation,
),
initialChildren: children,
);
static const String name = 'PlacesCollectionRoute';
@@ -1397,7 +1397,7 @@ class PlacesCollectionRouteArgs {
/// [RecentlyTakenPage]
class RecentlyTakenRoute extends PageRouteInfo<void> {
const RecentlyTakenRoute({List<PageRouteInfo>? children})
: super(RecentlyTakenRoute.name, initialChildren: children);
: super(RecentlyTakenRoute.name, initialChildren: children);
static const String name = 'RecentlyTakenRoute';
@@ -1413,7 +1413,7 @@ class RecentlyTakenRoute extends PageRouteInfo<void> {
/// [RemoteMediaSummaryPage]
class RemoteMediaSummaryRoute extends PageRouteInfo<void> {
const RemoteMediaSummaryRoute({List<PageRouteInfo>? children})
: super(RemoteMediaSummaryRoute.name, initialChildren: children);
: super(RemoteMediaSummaryRoute.name, initialChildren: children);
static const String name = 'RemoteMediaSummaryRoute';
@@ -1433,10 +1433,10 @@ class RemoteTimelineRoute extends PageRouteInfo<RemoteTimelineRouteArgs> {
required String albumId,
List<PageRouteInfo>? children,
}) : super(
RemoteTimelineRoute.name,
args: RemoteTimelineRouteArgs(key: key, albumId: albumId),
initialChildren: children,
);
RemoteTimelineRoute.name,
args: RemoteTimelineRouteArgs(key: key, albumId: albumId),
initialChildren: children,
);
static const String name = 'RemoteTimelineRoute';
@@ -1470,10 +1470,10 @@ class SearchRoute extends PageRouteInfo<SearchRouteArgs> {
SearchFilter? prefilter,
List<PageRouteInfo>? children,
}) : super(
SearchRoute.name,
args: SearchRouteArgs(key: key, prefilter: prefilter),
initialChildren: children,
);
SearchRoute.name,
args: SearchRouteArgs(key: key, prefilter: prefilter),
initialChildren: children,
);
static const String name = 'SearchRoute';
@@ -1505,7 +1505,7 @@ class SearchRouteArgs {
/// [SettingsPage]
class SettingsRoute extends PageRouteInfo<void> {
const SettingsRoute({List<PageRouteInfo>? children})
: super(SettingsRoute.name, initialChildren: children);
: super(SettingsRoute.name, initialChildren: children);
static const String name = 'SettingsRoute';
@@ -1525,10 +1525,10 @@ class SettingsSubRoute extends PageRouteInfo<SettingsSubRouteArgs> {
Key? key,
List<PageRouteInfo>? children,
}) : super(
SettingsSubRoute.name,
args: SettingsSubRouteArgs(section: section, key: key),
initialChildren: children,
);
SettingsSubRoute.name,
args: SettingsSubRouteArgs(section: section, key: key),
initialChildren: children,
);
static const String name = 'SettingsSubRoute';
@@ -1562,10 +1562,10 @@ class ShareIntentRoute extends PageRouteInfo<ShareIntentRouteArgs> {
required List<ShareIntentAttachment> attachments,
List<PageRouteInfo>? children,
}) : super(
ShareIntentRoute.name,
args: ShareIntentRouteArgs(key: key, attachments: attachments),
initialChildren: children,
);
ShareIntentRoute.name,
args: ShareIntentRouteArgs(key: key, attachments: attachments),
initialChildren: children,
);
static const String name = 'ShareIntentRoute';
@@ -1601,15 +1601,15 @@ class SharedLinkEditRoute extends PageRouteInfo<SharedLinkEditRouteArgs> {
String? albumId,
List<PageRouteInfo>? children,
}) : super(
SharedLinkEditRoute.name,
args: SharedLinkEditRouteArgs(
key: key,
existingLink: existingLink,
assetsList: assetsList,
albumId: albumId,
),
initialChildren: children,
);
SharedLinkEditRoute.name,
args: SharedLinkEditRouteArgs(
key: key,
existingLink: existingLink,
assetsList: assetsList,
albumId: albumId,
),
initialChildren: children,
);
static const String name = 'SharedLinkEditRoute';
@@ -1655,7 +1655,7 @@ class SharedLinkEditRouteArgs {
/// [SharedLinkPage]
class SharedLinkRoute extends PageRouteInfo<void> {
const SharedLinkRoute({List<PageRouteInfo>? children})
: super(SharedLinkRoute.name, initialChildren: children);
: super(SharedLinkRoute.name, initialChildren: children);
static const String name = 'SharedLinkRoute';
@@ -1671,7 +1671,7 @@ class SharedLinkRoute extends PageRouteInfo<void> {
/// [SplashScreenPage]
class SplashScreenRoute extends PageRouteInfo<void> {
const SplashScreenRoute({List<PageRouteInfo>? children})
: super(SplashScreenRoute.name, initialChildren: children);
: super(SplashScreenRoute.name, initialChildren: children);
static const String name = 'SplashScreenRoute';
@@ -1687,7 +1687,7 @@ class SplashScreenRoute extends PageRouteInfo<void> {
/// [TabControllerPage]
class TabControllerRoute extends PageRouteInfo<void> {
const TabControllerRoute({List<PageRouteInfo>? children})
: super(TabControllerRoute.name, initialChildren: children);
: super(TabControllerRoute.name, initialChildren: children);
static const String name = 'TabControllerRoute';
@@ -1703,7 +1703,7 @@ class TabControllerRoute extends PageRouteInfo<void> {
/// [TabShellPage]
class TabShellRoute extends PageRouteInfo<void> {
const TabShellRoute({List<PageRouteInfo>? children})
: super(TabShellRoute.name, initialChildren: children);
: super(TabShellRoute.name, initialChildren: children);
static const String name = 'TabShellRoute';
@@ -1719,7 +1719,7 @@ class TabShellRoute extends PageRouteInfo<void> {
/// [TrashPage]
class TrashRoute extends PageRouteInfo<void> {
const TrashRoute({List<PageRouteInfo>? children})
: super(TrashRoute.name, initialChildren: children);
: super(TrashRoute.name, initialChildren: children);
static const String name = 'TrashRoute';

View File

@@ -13,10 +13,12 @@ import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/isar_store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/drift_store.repository.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
@@ -48,15 +50,32 @@ abstract final class Bootstrap {
);
}
static Future<Drift> initDrift() async {
return Drift();
}
static Future<void> initDomain(
Isar db, {
bool shouldBufferLogs = true,
}) async {
final driftDb = Drift();
await StoreService.init(storeRepository: IsarStoreRepository(db));
await LogService.init(
logRepository: IsarLogRepository(db),
logRepository: LogRepository(driftDb),
storeRepository: IsarStoreRepository(db),
shouldBuffer: shouldBufferLogs,
);
}
static Future<void> initDomainWithDrift(
Drift db, {
bool shouldBufferLogs = true,
}) async {
await StoreService.init(storeRepository: DriftStoreRepository(db));
await LogService.init(
logRepository: LogRepository(db),
storeRepository: DriftStoreRepository(db),
shouldBuffer: shouldBufferLogs,
);
}
}

View File

@@ -14,8 +14,8 @@ import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/isar_store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/utils/diff.dart';

View File

@@ -1,12 +1,13 @@
import 'package:drift/drift.dart' hide isNull;
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:isar/isar.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/drift_store.repository.dart';
import '../../fixtures/user.stub.dart';
import '../../test_utils.dart';
const _kTestAccessToken = "#TestToken";
final _kTestBackupFailed = DateTime(2025, 2, 20, 11, 45);
@@ -14,16 +15,28 @@ const _kTestVersion = 10;
const _kTestColorfulInterface = false;
final _kTestUser = UserStub.admin;
Future<void> _addIntStoreValue(Isar db, StoreKey key, int? value) async {
await db.storeValues.put(StoreValue(key.id, intValue: value, strValue: null));
Future<void> _addIntStoreValue(Drift db, StoreKey key, int? value) async {
await db.into(db.storeEntity).insert(
StoreEntityCompanion.insert(
id: key.id,
intValue: Value(value),
strValue: const Value(null),
),
);
}
Future<void> _addStrStoreValue(Isar db, StoreKey key, String? value) async {
await db.storeValues.put(StoreValue(key.id, intValue: null, strValue: value));
Future<void> _addStrStoreValue(Drift db, StoreKey key, String? value) async {
await db.into(db.storeEntity).insert(
StoreEntityCompanion.insert(
id: key.id,
intValue: const Value(null),
strValue: Value(value),
),
);
}
Future<void> _populateStore(Isar db) async {
await db.writeTxn(() async {
Future<void> _populateStore(Drift db) async {
await db.transaction(() async {
await _addIntStoreValue(
db,
StoreKey.colorfulInterface,
@@ -40,12 +53,12 @@ Future<void> _populateStore(Isar db) async {
}
void main() {
late Isar db;
late IsarStoreRepository sut;
late Drift db;
late DriftStoreRepository sut;
setUp(() async {
db = await TestUtils.initIsar();
sut = IsarStoreRepository(db);
db = Drift(NativeDatabase.memory());
sut = DriftStoreRepository(db);
});
group('Store Repository converters:', () {
@@ -105,10 +118,16 @@ void main() {
});
test('deleteAll()', () async {
final count = await db.storeValues.count();
final countQuery = db.selectOnly(db.storeEntity)
..addColumns([db.storeEntity.id.count()]);
final countResult = await countQuery.getSingle();
final count = countResult.read(db.storeEntity.id.count()) ?? 0;
expect(count, isNot(isZero));
await sut.deleteAll();
expectLater(await db.storeValues.count(), isZero);
final newCountResult = await countQuery.getSingle();
final newCount = newCountResult.read(db.storeEntity.id.count()) ?? 0;
expect(newCount, isZero);
});
});

View File

@@ -12,7 +12,7 @@ import 'package:mocktail/mocktail.dart';
class MockStoreRepository extends Mock implements IsarStoreRepository {}
class MockLogRepository extends Mock implements IsarLogRepository {}
class MockLogRepository extends Mock implements LogRepository {}
class MockIsarUserRepository extends Mock implements IsarUserRepository {}

View File

@@ -1,3 +1,4 @@
import 'package:drift/native.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/constants/enums.dart';
@@ -8,6 +9,7 @@ import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/etag.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
@@ -80,12 +82,12 @@ void main() {
setUpAll(() async {
WidgetsFlutterBinding.ensureInitialized();
final db = await TestUtils.initIsar();
final driftDb = Drift(NativeDatabase.memory());
db.writeTxnSync(() => db.clearSync());
await StoreService.init(storeRepository: IsarStoreRepository(db));
await Store.put(StoreKey.currentUser, owner);
await LogService.init(
logRepository: IsarLogRepository(db),
logRepository: LogRepository(driftDb),
storeRepository: IsarStoreRepository(db),
);
});

View File

@@ -15,7 +15,6 @@ import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/log.entity.dart';
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:isar/isar.dart';
import 'package:mocktail/mocktail.dart';
@@ -41,7 +40,6 @@ abstract final class TestUtils {
final db = await Isar.open(
[
StoreValueSchema,
ExifInfoSchema,
AssetSchema,
AlbumSchema,