Show first 100 photos on startup

This is to create an illusion that the app loads quickly when it's not ;)
This commit is contained in:
Ming Ming 2022-07-14 04:23:41 +08:00
parent 4fa84c4f01
commit 7563fa2ad6
7 changed files with 145 additions and 22 deletions

View file

@ -186,6 +186,17 @@ class ScanAccountDirBloc
final hasContent = state.files.isNotEmpty;
final stopwatch = Stopwatch()..start();
if (!hasContent) {
try {
emit(ScanAccountDirBlocLoading(await _queryOfflineMini(ev)));
} catch (e, stackTrace) {
_log.shout(
"[_onEventQuery] Failed while _queryOfflineMini", e, stackTrace);
}
_log.info(
"[_onEventQuery] Elapsed time (_queryOfflineMini): ${stopwatch.elapsedMilliseconds}ms");
stopwatch.reset();
}
final cacheFiles = await _queryOffline(ev);
_log.info(
"[_onEventQuery] Elapsed time (_queryOffline): ${stopwatch.elapsedMilliseconds}ms");
@ -322,6 +333,16 @@ class ScanAccountDirBloc
);
}
/// Query a small amount of files to give an illusion of quick startup
Future<List<File>> _queryOfflineMini(ScanAccountDirBlocQueryBase ev) async {
return await ScanDirOfflineMini(_c)(
account,
account.roots.map((r) => File(path: file_util.unstripPath(account, r))),
100,
isOnlySupportedFormat: true,
);
}
Future<List<File>> _queryOffline(ScanAccountDirBlocQueryBase ev) async {
final files = <File>[];
for (final r in account.roots) {

View file

@ -308,15 +308,12 @@ class FileSqliteDbDataSource implements FileDataSource {
Account account, int fromEpochMs, int toEpochMs) async {
_log.info("[listByDate] [$fromEpochMs, $toEpochMs]");
final dbFiles = await _c.sqliteDb.use((db) async {
final dateTime = sql.coalesce([
db.accountFiles.overrideDateTime,
db.images.dateTimeOriginal,
db.files.lastModified,
]).secondsSinceEpoch;
final queryBuilder = db.queryFiles()
..setQueryMode(sql.FilesQueryMode.completeFile)
..setAppAccount(account);
final query = queryBuilder.build();
final query = db.queryFiles().run((q) {
q.setQueryMode(sql.FilesQueryMode.completeFile);
q.setAppAccount(account);
return q.build();
});
final dateTime = db.accountFiles.bestDateTime.secondsSinceEpoch;
query
..where(dateTime.isBetweenValues(
fromEpochMs ~/ 1000, (toEpochMs ~/ 1000) - 1))

View file

@ -58,6 +58,8 @@ class AccountFiles extends Table {
BoolColumn get isArchived => boolean().nullable()();
DateTimeColumn get overrideDateTime =>
dateTime().map(const _DateTimeConverter()).nullable()();
DateTimeColumn get bestDateTime =>
dateTime().map(const _DateTimeConverter())();
@override
get uniqueKeys => [
@ -186,6 +188,8 @@ class SqliteDb extends _$SqliteDb {
"CREATE INDEX account_files_file_index ON account_files(file);"));
await m.createIndex(Index("account_files_relative_path_index",
"CREATE INDEX account_files_relative_path_index ON account_files(relative_path);"));
await m.createIndex(Index("account_files_best_date_time_index",
"CREATE INDEX account_files_best_date_time_index ON account_files(best_date_time);"));
await m.createIndex(Index("dir_files_dir_index",
"CREATE INDEX dir_files_dir_index ON dir_files(dir);"));

View file

@ -1000,6 +1000,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
final bool? isFavorite;
final bool? isArchived;
final DateTime? overrideDateTime;
final DateTime bestDateTime;
AccountFile(
{required this.rowId,
required this.account,
@ -1007,7 +1008,8 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
required this.relativePath,
this.isFavorite,
this.isArchived,
this.overrideDateTime});
this.overrideDateTime,
required this.bestDateTime});
factory AccountFile.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return AccountFile(
@ -1026,6 +1028,9 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
overrideDateTime: $AccountFilesTable.$converter0.mapToDart(
const DateTimeType().mapFromDatabaseResponse(
data['${effectivePrefix}override_date_time'])),
bestDateTime: $AccountFilesTable.$converter1.mapToDart(
const DateTimeType().mapFromDatabaseResponse(
data['${effectivePrefix}best_date_time']))!,
);
}
@override
@ -1046,6 +1051,11 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
map['override_date_time'] =
Variable<DateTime?>(converter.mapToSql(overrideDateTime));
}
{
final converter = $AccountFilesTable.$converter1;
map['best_date_time'] =
Variable<DateTime>(converter.mapToSql(bestDateTime)!);
}
return map;
}
@ -1064,6 +1074,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
overrideDateTime: overrideDateTime == null && nullToAbsent
? const Value.absent()
: Value(overrideDateTime),
bestDateTime: Value(bestDateTime),
);
}
@ -1079,6 +1090,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
isArchived: serializer.fromJson<bool?>(json['isArchived']),
overrideDateTime:
serializer.fromJson<DateTime?>(json['overrideDateTime']),
bestDateTime: serializer.fromJson<DateTime>(json['bestDateTime']),
);
}
@override
@ -1092,6 +1104,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
'isFavorite': serializer.toJson<bool?>(isFavorite),
'isArchived': serializer.toJson<bool?>(isArchived),
'overrideDateTime': serializer.toJson<DateTime?>(overrideDateTime),
'bestDateTime': serializer.toJson<DateTime>(bestDateTime),
};
}
@ -1102,7 +1115,8 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
String? relativePath,
Value<bool?> isFavorite = const Value.absent(),
Value<bool?> isArchived = const Value.absent(),
Value<DateTime?> overrideDateTime = const Value.absent()}) =>
Value<DateTime?> overrideDateTime = const Value.absent(),
DateTime? bestDateTime}) =>
AccountFile(
rowId: rowId ?? this.rowId,
account: account ?? this.account,
@ -1113,6 +1127,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
overrideDateTime: overrideDateTime.present
? overrideDateTime.value
: this.overrideDateTime,
bestDateTime: bestDateTime ?? this.bestDateTime,
);
@override
String toString() {
@ -1123,14 +1138,15 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
..write('relativePath: $relativePath, ')
..write('isFavorite: $isFavorite, ')
..write('isArchived: $isArchived, ')
..write('overrideDateTime: $overrideDateTime')
..write('overrideDateTime: $overrideDateTime, ')
..write('bestDateTime: $bestDateTime')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(rowId, account, file, relativePath,
isFavorite, isArchived, overrideDateTime);
isFavorite, isArchived, overrideDateTime, bestDateTime);
@override
bool operator ==(Object other) =>
identical(this, other) ||
@ -1141,7 +1157,8 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
other.relativePath == this.relativePath &&
other.isFavorite == this.isFavorite &&
other.isArchived == this.isArchived &&
other.overrideDateTime == this.overrideDateTime);
other.overrideDateTime == this.overrideDateTime &&
other.bestDateTime == this.bestDateTime);
}
class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
@ -1152,6 +1169,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
final Value<bool?> isFavorite;
final Value<bool?> isArchived;
final Value<DateTime?> overrideDateTime;
final Value<DateTime> bestDateTime;
const AccountFilesCompanion({
this.rowId = const Value.absent(),
this.account = const Value.absent(),
@ -1160,6 +1178,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
this.isFavorite = const Value.absent(),
this.isArchived = const Value.absent(),
this.overrideDateTime = const Value.absent(),
this.bestDateTime = const Value.absent(),
});
AccountFilesCompanion.insert({
this.rowId = const Value.absent(),
@ -1169,9 +1188,11 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
this.isFavorite = const Value.absent(),
this.isArchived = const Value.absent(),
this.overrideDateTime = const Value.absent(),
required DateTime bestDateTime,
}) : account = Value(account),
file = Value(file),
relativePath = Value(relativePath);
relativePath = Value(relativePath),
bestDateTime = Value(bestDateTime);
static Insertable<AccountFile> custom({
Expression<int>? rowId,
Expression<int>? account,
@ -1180,6 +1201,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
Expression<bool?>? isFavorite,
Expression<bool?>? isArchived,
Expression<DateTime?>? overrideDateTime,
Expression<DateTime>? bestDateTime,
}) {
return RawValuesInsertable({
if (rowId != null) 'row_id': rowId,
@ -1189,6 +1211,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
if (isFavorite != null) 'is_favorite': isFavorite,
if (isArchived != null) 'is_archived': isArchived,
if (overrideDateTime != null) 'override_date_time': overrideDateTime,
if (bestDateTime != null) 'best_date_time': bestDateTime,
});
}
@ -1199,7 +1222,8 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
Value<String>? relativePath,
Value<bool?>? isFavorite,
Value<bool?>? isArchived,
Value<DateTime?>? overrideDateTime}) {
Value<DateTime?>? overrideDateTime,
Value<DateTime>? bestDateTime}) {
return AccountFilesCompanion(
rowId: rowId ?? this.rowId,
account: account ?? this.account,
@ -1208,6 +1232,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
isFavorite: isFavorite ?? this.isFavorite,
isArchived: isArchived ?? this.isArchived,
overrideDateTime: overrideDateTime ?? this.overrideDateTime,
bestDateTime: bestDateTime ?? this.bestDateTime,
);
}
@ -1237,6 +1262,11 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
map['override_date_time'] =
Variable<DateTime?>(converter.mapToSql(overrideDateTime.value));
}
if (bestDateTime.present) {
final converter = $AccountFilesTable.$converter1;
map['best_date_time'] =
Variable<DateTime>(converter.mapToSql(bestDateTime.value)!);
}
return map;
}
@ -1249,7 +1279,8 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
..write('relativePath: $relativePath, ')
..write('isFavorite: $isFavorite, ')
..write('isArchived: $isArchived, ')
..write('overrideDateTime: $overrideDateTime')
..write('overrideDateTime: $overrideDateTime, ')
..write('bestDateTime: $bestDateTime')
..write(')'))
.toString();
}
@ -1310,6 +1341,14 @@ class $AccountFilesTable extends AccountFiles
'override_date_time', aliasedName, true,
type: const IntType(), requiredDuringInsert: false)
.withConverter<DateTime>($AccountFilesTable.$converter0);
final VerificationMeta _bestDateTimeMeta =
const VerificationMeta('bestDateTime');
@override
late final GeneratedColumnWithTypeConverter<DateTime, DateTime?>
bestDateTime = GeneratedColumn<DateTime?>(
'best_date_time', aliasedName, false,
type: const IntType(), requiredDuringInsert: true)
.withConverter<DateTime>($AccountFilesTable.$converter1);
@override
List<GeneratedColumn> get $columns => [
rowId,
@ -1318,7 +1357,8 @@ class $AccountFilesTable extends AccountFiles
relativePath,
isFavorite,
isArchived,
overrideDateTime
overrideDateTime,
bestDateTime
];
@override
String get aliasedName => _alias ?? 'account_files';
@ -1366,6 +1406,7 @@ class $AccountFilesTable extends AccountFiles
data['is_archived']!, _isArchivedMeta));
}
context.handle(_overrideDateTimeMeta, const VerificationResult.success());
context.handle(_bestDateTimeMeta, const VerificationResult.success());
return context;
}
@ -1388,6 +1429,8 @@ class $AccountFilesTable extends AccountFiles
static TypeConverter<DateTime, DateTime> $converter0 =
const _DateTimeConverter();
static TypeConverter<DateTime, DateTime> $converter1 =
const _DateTimeConverter();
}
class Image extends DataClass implements Insertable<Image> {

View file

@ -122,6 +122,7 @@ class SqliteFileConverter {
isFavorite: Value(file.isFavorite),
isArchived: Value(file.isArchived),
overrideDateTime: Value(file.overrideDateTime),
bestDateTime: Value(file.bestDateTime),
);
final dbImage = file.metadata?.run((m) => sql.ImagesCompanion.insert(
lastUpdated: m.lastUpdated,

View file

@ -466,7 +466,7 @@ class FilesQueryBuilder {
}
void byRelativePathPattern(String pattern) {
_byRelativePathPattern = pattern;
(_byRelativePathPatterns ??= []).add(pattern);
}
void byMimePattern(String pattern) {
@ -536,8 +536,14 @@ class FilesQueryBuilder {
if (_byRelativePath != null) {
query.where(db.accountFiles.relativePath.equals(_byRelativePath));
}
if (_byRelativePathPattern != null) {
query.where(db.accountFiles.relativePath.like(_byRelativePathPattern!));
if (_byRelativePathPatterns?.isNotEmpty == true) {
final expression = _byRelativePathPatterns!
.sublist(1)
.fold<Expression<bool?>>(
db.accountFiles.relativePath.like(_byRelativePathPatterns![0]),
(previousValue, element) =>
previousValue | db.accountFiles.relativePath.like(element));
query.where(expression);
}
if (_byMimePatterns?.isNotEmpty == true) {
final expression = _byMimePatterns!.sublist(1).fold<Expression<bool?>>(
@ -576,7 +582,7 @@ class FilesQueryBuilder {
int? _byFileId;
Iterable<int>? _byFileIds;
String? _byRelativePath;
String? _byRelativePathPattern;
List<String>? _byRelativePathPatterns;
List<String>? _byMimePatterns;
bool? _byFavorite;
int? _byDirRowId;

View file

@ -1,3 +1,4 @@
import 'package:drift/drift.dart' as sql;
import 'package:nc_photos/account.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/file.dart';
@ -57,3 +58,53 @@ class ScanDirOffline {
final DiContainer _c;
}
class ScanDirOfflineMini {
ScanDirOfflineMini(this._c) : assert(require(_c));
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
Future<List<File>> call(
Account account,
Iterable<File> roots,
int limit, {
bool isOnlySupportedFormat = true,
}) async {
final dbFiles = await _c.sqliteDb.use((db) async {
final query = db.queryFiles().run((q) {
q
..setQueryMode(sql.FilesQueryMode.completeFile)
..setAppAccount(account);
for (final r in roots) {
final path = r.strippedPathWithEmpty;
if (path.isEmpty) {
break;
}
q.byRelativePathPattern("$path/%");
}
if (isOnlySupportedFormat) {
q
..byMimePattern("image/%")
..byMimePattern("video/%");
}
return q.build();
});
query
..orderBy([sql.OrderingTerm.desc(db.accountFiles.bestDateTime)])
..limit(limit);
return await query
.map((r) => sql.CompleteFile(
r.readTable(db.files),
r.readTable(db.accountFiles),
r.readTableOrNull(db.images),
r.readTableOrNull(db.trashes),
))
.get();
});
return dbFiles
.map((f) => SqliteFileConverter.fromSql(account.userId.toString(), f))
.toList();
}
final DiContainer _c;
}