feat: home grid

This commit is contained in:
shenlong-tanwen
2024-09-10 01:14:05 +05:30
parent 80009a77ec
commit 419d3669a2
33 changed files with 808 additions and 429 deletions
@@ -0,0 +1,137 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/entities/asset.entity.drift.dart';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/domain/models/render_list.model.dart';
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/extensions/drift.extension.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
import 'package:intl/intl.dart';
class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
final DriftDatabaseRepository _db;
const RemoteAssetDriftRepository(this._db);
@override
Future<bool> addAll(Iterable<Asset> assets) async {
try {
await _db.batch((batch) => batch.insertAllOnConflictUpdate(
_db.asset,
assets.map(_toEntity),
));
return true;
} catch (e, s) {
log.severe("Cannot insert remote assets into table", e, s);
return false;
}
}
@override
Future<bool> clearAll() async {
try {
await _db.asset.deleteAll();
return true;
} catch (e, s) {
log.severe("Cannot clear remote assets", e, s);
return false;
}
}
@override
Future<List<Asset>> fetchAssets({int? offset, int? limit}) async {
final query = _db.asset.select()
..orderBy([(asset) => OrderingTerm.desc(asset.createdTime)]);
if (limit != null) {
query.limit(limit, offset: offset);
}
return (await query.get()).map(_toModel).toList();
}
@override
Stream<RenderList> getRenderList() {
final assetCountExp = _db.asset.id.count();
final createdTimeExp = _db.asset.createdTime;
final monthYearExp = _db.asset.createdTime.strftime('%m-%Y');
final query = _db.asset.selectOnly()
..addColumns([assetCountExp, createdTimeExp])
..groupBy([monthYearExp])
..orderBy([OrderingTerm.desc(createdTimeExp)]);
int lastAssetOffset = 0;
final monthFormatter = DateFormat.yMMMM();
return query
.expand((row) {
final createdTime = row.read<DateTime>(createdTimeExp)!;
final assetCount = row.read(assetCountExp)!;
final assetOffset = lastAssetOffset;
lastAssetOffset += assetCount;
return [
RenderListMonthHeaderElement(
header: monthFormatter.format(createdTime),
),
RenderListAssetElement(
date: createdTime,
assetCount: assetCount,
assetOffset: assetOffset,
),
];
})
.watch()
.map((elements) {
final int totalCount;
final lastAssetElement =
elements.whereType<RenderListAssetElement>().lastOrNull;
if (lastAssetElement == null) {
totalCount = 0;
} else {
totalCount =
lastAssetElement.assetCount + lastAssetElement.assetOffset;
}
return RenderList(elements: elements, totalCount: totalCount);
});
}
}
AssetCompanion _toEntity(Asset asset) {
return AssetCompanion.insert(
localId: Value(asset.localId),
remoteId: Value(asset.remoteId),
name: asset.name,
checksum: asset.checksum,
height: Value(asset.height),
width: Value(asset.width),
type: asset.type,
createdTime: asset.createdTime,
duration: Value(asset.duration),
modifiedTime: Value(asset.modifiedTime),
livePhotoVideoId: Value(asset.livePhotoVideoId),
);
}
Asset _toModel(AssetData asset) {
return Asset(
id: asset.id,
localId: asset.localId,
remoteId: asset.remoteId,
name: asset.name,
type: asset.type,
checksum: asset.checksum,
createdTime: asset.createdTime,
modifiedTime: asset.modifiedTime,
height: asset.height,
width: asset.width,
livePhotoVideoId: asset.livePhotoVideoId,
duration: asset.duration,
);
}
@@ -4,15 +4,14 @@ import 'package:drift_dev/api/migrations.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/entities/album.entity.dart';
import 'package:immich_mobile/domain/entities/local_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/remote_asset.entity.dart';
import 'package:immich_mobile/domain/entities/store.entity.dart';
import 'package:immich_mobile/domain/entities/user.entity.dart';
import 'database.repository.drift.dart';
@DriftDatabase(tables: [Logs, Store, LocalAlbum, LocalAsset, User, RemoteAsset])
@DriftDatabase(tables: [Logs, Store, LocalAlbum, Asset, User])
class DriftDatabaseRepository extends $DriftDatabaseRepository {
DriftDatabaseRepository([QueryExecutor? executor])
: super(executor ?? driftDatabase(name: 'db'));
@@ -27,6 +26,9 @@ class DriftDatabaseRepository extends $DriftDatabaseRepository {
if (kDebugMode) {
await validateDatabaseSchema();
}
await customStatement('PRAGMA journal_mode = WAL');
await customStatement('PRAGMA foreign_keys = ON');
},
// ignore: no-empty-block
onUpgrade: (m, from, to) async {},
@@ -8,21 +8,21 @@ import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart';
class LogDriftRepository implements ILogRepository {
final DriftDatabaseRepository db;
final DriftDatabaseRepository _db;
const LogDriftRepository(this.db);
const LogDriftRepository(this._db);
@override
Future<List<LogMessage>> fetchAll() async {
return await db.managers.logs.map(_toModel).get();
return await _db.managers.logs.map(_toModel).get();
}
@override
Future<void> truncateLogs({int limit = 250}) async {
final totalCount = await db.managers.logs.count();
final totalCount = await _db.managers.logs.count();
if (totalCount > limit) {
final rowsToDelete = totalCount - limit;
await db.managers.logs
await _db.managers.logs
.orderBy((o) => o.createdAt.desc())
.limit(rowsToDelete)
.delete();
@@ -32,7 +32,7 @@ class LogDriftRepository implements ILogRepository {
@override
FutureOr<bool> add(LogMessage log) async {
try {
await db.into(db.logs).insert(LogsCompanion.insert(
await _db.into(_db.logs).insert(LogsCompanion.insert(
content: log.content,
level: log.level,
createdAt: Value(log.createdAt),
@@ -50,9 +50,9 @@ class LogDriftRepository implements ILogRepository {
@override
FutureOr<bool> addAll(List<LogMessage> logs) async {
try {
await db.batch((b) {
await _db.batch((b) {
b.insertAll(
db.logs,
_db.logs,
logs.map((log) => LogsCompanion.insert(
content: log.content,
level: log.level,
@@ -73,7 +73,7 @@ class LogDriftRepository implements ILogRepository {
@override
FutureOr<bool> clear() async {
try {
await db.managers.logs.delete();
await _db.managers.logs.delete();
return true;
} catch (e) {
debugPrint("Error while clearning the logs in DB - $e");
@@ -1,44 +0,0 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/entities/remote_asset.entity.drift.dart';
import 'package:immich_mobile/domain/interfaces/remote_asset.interface.dart';
import 'package:immich_mobile/domain/models/asset/remote_asset.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
class RemoteAssetDriftRepository
with LogContext
implements IRemoteAssetRepository {
final DriftDatabaseRepository _db;
const RemoteAssetDriftRepository(this._db);
@override
Future<bool> addAll(Iterable<RemoteAsset> assets) async {
try {
await _db.batch((batch) => batch.insertAllOnConflictUpdate(
_db.remoteAsset,
assets.map(_toEntity),
));
return true;
} catch (e, s) {
log.severe("Cannot insert remote assets into table", e, s);
return false;
}
}
}
RemoteAssetCompanion _toEntity(RemoteAsset asset) {
return RemoteAssetCompanion.insert(
name: asset.name,
checksum: asset.checksum,
height: Value(asset.height),
width: Value(asset.width),
type: asset.type,
createdTime: asset.createdTime,
remoteId: asset.remoteId,
duration: Value(asset.duration),
modifiedTime: Value(asset.modifiedTime),
livePhotoVideoId: Value(asset.livePhotoVideoId),
);
}
@@ -8,13 +8,13 @@ import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
class StoreDriftRepository with LogContext implements IStoreRepository {
final DriftDatabaseRepository db;
final DriftDatabaseRepository _db;
const StoreDriftRepository(this.db);
const StoreDriftRepository(this._db);
@override
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))
.getSingleOrNull();
return _getValueFromStoreData(key, storeData);
@@ -35,7 +35,7 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
final storeValue = key.converter.toPrimitive(value);
final intValue = (key.type == int) ? storeValue as int : null;
final stringValue = (key.type == String) ? storeValue as String : null;
await db.into(db.store).insertOnConflictUpdate(StoreCompanion.insert(
await _db.into(_db.store).insertOnConflictUpdate(StoreCompanion.insert(
id: Value(key.id),
intValue: Value(intValue),
stringValue: Value(stringValue),
@@ -49,12 +49,12 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
@override
FutureOr<void> delete(StoreKey key) 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
Stream<T?> watch<T, U>(StoreKey<T, U> key) {
return db.managers.store
return _db.managers.store
.filter((s) => s.id.equals(key.id))
.watchSingleOrNull()
.asyncMap((e) async => await _getValueFromStoreData(key, e));
@@ -62,8 +62,7 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
@override
FutureOr<void> clearStore() async {
await db.managers.store.delete();
await _db.managers.store.delete();
}
FutureOr<T?> _getValueFromStoreData<T, U>(
@@ -8,13 +8,13 @@ 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;
final DriftDatabaseRepository _db;
const UserDriftRepository(this.db);
const UserDriftRepository(this._db);
@override
FutureOr<User?> fetch(String userId) async {
return await db.managers.user
return await _db.managers.user
.filter((f) => f.id.equals(userId))
.map(_toModel)
.getSingleOrNull();
@@ -23,7 +23,7 @@ class UserDriftRepository with LogContext implements IUserRepository {
@override
FutureOr<bool> add(User user) async {
try {
await db.into(db.user).insertOnConflictUpdate(
await _db.into(_db.user).insertOnConflictUpdate(
UserCompanion.insert(
id: user.id,
name: user.name,