refactor: asset grid

This commit is contained in:
shenlong-tanwen
2024-09-14 22:29:51 +05:30
parent 53974e7276
commit 6fce1ebb79
23 changed files with 796 additions and 113 deletions
@@ -1,5 +1,5 @@
import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/domain/services/render_list.service.dart';
import 'package:immich_mobile/domain/models/render_list.model.dart';
abstract class IAssetRepository {
/// Batch insert asset
@@ -12,5 +12,5 @@ abstract class IAssetRepository {
Future<List<Asset>> fetchAssets({int? offset, int? limit});
/// Streams assets as groups grouped by the group type passed
Stream<RenderList> getRenderList();
Stream<RenderList> watchRenderList();
}
@@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/presentation/modules/theme/models/app_theme.model.dart';
// AppSetting needs to store UI specific settings as well as domain specific settings
// This model is the only exclusion which refers to entities from the presentation layer
// as well as the domain layer
enum AppSetting<T> {
appTheme<AppTheme>(StoreKey.appTheme, AppTheme.blue),
themeMode<ThemeMode>(StoreKey.themeMode, ThemeMode.system),
@@ -0,0 +1,21 @@
import 'package:collection/collection.dart';
import 'package:immich_mobile/domain/models/render_list_element.model.dart';
class RenderList {
final List<RenderListElement> elements;
late final int totalCount;
RenderList({required this.elements}) {
final lastAssetElement =
elements.whereType<RenderListAssetElement>().lastOrNull;
if (lastAssetElement == null) {
totalCount = 0;
} else {
totalCount = lastAssetElement.assetCount + lastAssetElement.assetOffset;
}
}
factory RenderList.empty() {
return RenderList(elements: []);
}
}
@@ -28,6 +28,10 @@ class RenderListMonthHeaderElement extends RenderListElement {
header = formatter.format(date);
}
@override
String toString() =>
'RenderListMonthHeaderElement(header: $header, date: $date)';
@override
bool operator ==(covariant RenderListMonthHeaderElement other) {
if (identical(this, other)) return true;
@@ -44,6 +48,10 @@ class RenderListDayHeaderElement extends RenderListElement {
const RenderListDayHeaderElement({required super.date, required this.header});
@override
String toString() =>
'RenderListDayHeaderElement(header: $header, date: $date)';
@override
bool operator ==(covariant RenderListDayHeaderElement other) {
if (identical(this, other)) return true;
@@ -4,9 +4,9 @@ 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/domain/services/render_list.service.dart';
import 'package:immich_mobile/utils/extensions/drift.extension.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
@@ -54,7 +54,7 @@ class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
}
@override
Stream<RenderList> getRenderList() {
Stream<RenderList> watchRenderList() {
final assetCountExp = _db.asset.id.count();
final createdTimeExp = _db.asset.createdTime;
final monthYearExp = _db.asset.createdTime.strftime('%m-%Y');
@@ -83,19 +83,7 @@ class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
];
})
.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);
});
.map((elements) => RenderList(elements: elements));
}
}
@@ -1,64 +0,0 @@
import 'dart:math' as math;
import 'package:collection/collection.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_element.model.dart';
import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/constants/globals.dart';
class RenderList {
final List<RenderListElement> elements;
final int totalCount;
/// offset of the assets from last section in [_buf]
int _bufOffset = 0;
/// assets cache loaded from DB with offset [_bufOffset]
List<Asset> _buf = [];
RenderList({required this.elements, required this.totalCount});
/// Loads the requested assets from the database to an internal buffer if not cached
/// and returns a slice of that buffer
Future<List<Asset>> loadAssets(int offset, int count) async {
assert(offset >= 0);
assert(count > 0);
assert(offset + count <= totalCount);
// the requested slice (offset:offset+count) is not contained in the cache buffer `_buf`
// thus, fill the buffer with a new batch of assets that at least contains the requested
// assets and some more
if (offset < _bufOffset || offset + count > _bufOffset + _buf.length) {
final bool forward = _bufOffset < offset;
// make sure to load a meaningful amount of data (and not only the requested slice)
// otherwise, each call to [loadAssets] would result in DB call trashing performance
// fills small requests to [batchSize], adds some legroom into the opposite scroll direction for large requests
final len =
math.max(kRenderListBatchSize, count + kRenderListOppositeBatchSize);
// when scrolling forward, start shortly before the requested offset...
// when scrolling backward, end shortly after the requested offset...
// ... to guard against the user scrolling in the other direction
// a tiny bit resulting in a another required load from the DB
final start = math.max(
0,
forward
? offset - kRenderListOppositeBatchSize
: (len > kRenderListBatchSize ? offset : offset + count - len),
);
// load the calculated batch (start:start+len) from the DB and put it into the buffer
_buf =
await di<IAssetRepository>().fetchAssets(offset: start, limit: len);
_bufOffset = start;
assert(_bufOffset <= offset);
assert(_bufOffset + _buf.length >= offset + count);
}
// return the requested slice from the buffer (we made sure before that the assets are loaded!)
return _buf.slice(offset - _bufOffset, offset - _bufOffset + count);
}
}