mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 05:42:11 +00:00
Improved: Media thumbnails for faster loading
This commit is contained in:
parent
f3b64646f5
commit
5556532879
17 changed files with 14922 additions and 134 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## 0.2.13
|
||||
|
||||
- Improved: Media thumbnails for faster loading
|
||||
|
||||
## 0.2.12
|
||||
|
||||
- New: Automatically mark identical media as opened across all chats (Settings > Chats).
|
||||
|
|
|
|||
|
|
@ -114,16 +114,15 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
.get();
|
||||
}
|
||||
|
||||
Future<List<MediaFile>> getAllNonHashedStoredMediaFiles() async {
|
||||
Future<List<MediaFile>> getAllMediaFilesPendingMigration() async {
|
||||
return (select(mediaFiles)..where(
|
||||
(t) => t.stored.equals(true) & t.storedFileHash.isNull(),
|
||||
))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<List<MediaFile>> getAllUnanalyzedStoredMediaFiles() async {
|
||||
return (select(mediaFiles)..where(
|
||||
(t) => t.stored.equals(true) & t.hasCropAnalyzed.equals(false),
|
||||
(t) =>
|
||||
t.stored.equals(true) &
|
||||
(t.storedFileHash.isNull() |
|
||||
t.hasCropAnalyzed.equals(false) |
|
||||
(t.hasThumbnail.equals(false) &
|
||||
t.type.equals(MediaType.audio.name).not()) |
|
||||
t.sizeInBytes.isNull()),
|
||||
))
|
||||
.get();
|
||||
}
|
||||
|
|
|
|||
3019
lib/src/database/schemas/twonly_db/drift_schema_v16.json
Normal file
3019
lib/src/database/schemas/twonly_db/drift_schema_v16.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -69,6 +69,11 @@ class MediaFiles extends Table {
|
|||
|
||||
BlobColumn get storedFileHash => blob().nullable()();
|
||||
|
||||
BoolColumn get hasThumbnail =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
IntColumn get sizeInBytes => integer().nullable()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
TextColumn get createdAtMonth => text().nullable()();
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 15;
|
||||
int get schemaVersion => 16;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
|
|
@ -211,6 +211,13 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
from14To15: (m, schema) async {
|
||||
await m.createTable(schema.signalSignedPreKeyStores);
|
||||
},
|
||||
from15To16: (m, schema) async {
|
||||
await m.addColumn(
|
||||
schema.mediaFiles,
|
||||
schema.mediaFiles.hasThumbnail,
|
||||
);
|
||||
await m.addColumn(schema.mediaFiles, schema.mediaFiles.sizeInBytes);
|
||||
},
|
||||
)(m, from, to);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2810,6 +2810,32 @@ class $MediaFilesTable extends MediaFiles
|
|||
type: DriftSqlType.blob,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _hasThumbnailMeta = const VerificationMeta(
|
||||
'hasThumbnail',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<bool> hasThumbnail = GeneratedColumn<bool>(
|
||||
'has_thumbnail',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("has_thumbnail" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const Constant(false),
|
||||
);
|
||||
static const VerificationMeta _sizeInBytesMeta = const VerificationMeta(
|
||||
'sizeInBytes',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<int> sizeInBytes = GeneratedColumn<int>(
|
||||
'size_in_bytes',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _createdAtMeta = const VerificationMeta(
|
||||
'createdAt',
|
||||
);
|
||||
|
|
@ -2853,6 +2879,8 @@ class $MediaFilesTable extends MediaFiles
|
|||
encryptionMac,
|
||||
encryptionNonce,
|
||||
storedFileHash,
|
||||
hasThumbnail,
|
||||
sizeInBytes,
|
||||
createdAt,
|
||||
createdAtMonth,
|
||||
];
|
||||
|
|
@ -2987,6 +3015,24 @@ class $MediaFilesTable extends MediaFiles
|
|||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('has_thumbnail')) {
|
||||
context.handle(
|
||||
_hasThumbnailMeta,
|
||||
hasThumbnail.isAcceptableOrUnknown(
|
||||
data['has_thumbnail']!,
|
||||
_hasThumbnailMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('size_in_bytes')) {
|
||||
context.handle(
|
||||
_sizeInBytesMeta,
|
||||
sizeInBytes.isAcceptableOrUnknown(
|
||||
data['size_in_bytes']!,
|
||||
_sizeInBytesMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(
|
||||
_createdAtMeta,
|
||||
|
|
@ -3092,6 +3138,14 @@ class $MediaFilesTable extends MediaFiles
|
|||
DriftSqlType.blob,
|
||||
data['${effectivePrefix}stored_file_hash'],
|
||||
),
|
||||
hasThumbnail: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}has_thumbnail'],
|
||||
)!,
|
||||
sizeInBytes: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}size_in_bytes'],
|
||||
),
|
||||
createdAt: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}created_at'],
|
||||
|
|
@ -3147,6 +3201,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
final Uint8List? encryptionMac;
|
||||
final Uint8List? encryptionNonce;
|
||||
final Uint8List? storedFileHash;
|
||||
final bool hasThumbnail;
|
||||
final int? sizeInBytes;
|
||||
final DateTime createdAt;
|
||||
final String? createdAtMonth;
|
||||
const MediaFile({
|
||||
|
|
@ -3168,6 +3224,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
this.encryptionMac,
|
||||
this.encryptionNonce,
|
||||
this.storedFileHash,
|
||||
required this.hasThumbnail,
|
||||
this.sizeInBytes,
|
||||
required this.createdAt,
|
||||
this.createdAtMonth,
|
||||
});
|
||||
|
|
@ -3228,6 +3286,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
if (!nullToAbsent || storedFileHash != null) {
|
||||
map['stored_file_hash'] = Variable<Uint8List>(storedFileHash);
|
||||
}
|
||||
map['has_thumbnail'] = Variable<bool>(hasThumbnail);
|
||||
if (!nullToAbsent || sizeInBytes != null) {
|
||||
map['size_in_bytes'] = Variable<int>(sizeInBytes);
|
||||
}
|
||||
map['created_at'] = Variable<DateTime>(createdAt);
|
||||
if (!nullToAbsent || createdAtMonth != null) {
|
||||
map['created_at_month'] = Variable<String>(createdAtMonth);
|
||||
|
|
@ -3278,6 +3340,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
storedFileHash: storedFileHash == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(storedFileHash),
|
||||
hasThumbnail: Value(hasThumbnail),
|
||||
sizeInBytes: sizeInBytes == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(sizeInBytes),
|
||||
createdAt: Value(createdAt),
|
||||
createdAtMonth: createdAtMonth == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
|
|
@ -3323,6 +3389,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
encryptionMac: serializer.fromJson<Uint8List?>(json['encryptionMac']),
|
||||
encryptionNonce: serializer.fromJson<Uint8List?>(json['encryptionNonce']),
|
||||
storedFileHash: serializer.fromJson<Uint8List?>(json['storedFileHash']),
|
||||
hasThumbnail: serializer.fromJson<bool>(json['hasThumbnail']),
|
||||
sizeInBytes: serializer.fromJson<int?>(json['sizeInBytes']),
|
||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||
createdAtMonth: serializer.fromJson<String?>(json['createdAtMonth']),
|
||||
);
|
||||
|
|
@ -3357,6 +3425,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
'encryptionMac': serializer.toJson<Uint8List?>(encryptionMac),
|
||||
'encryptionNonce': serializer.toJson<Uint8List?>(encryptionNonce),
|
||||
'storedFileHash': serializer.toJson<Uint8List?>(storedFileHash),
|
||||
'hasThumbnail': serializer.toJson<bool>(hasThumbnail),
|
||||
'sizeInBytes': serializer.toJson<int?>(sizeInBytes),
|
||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||
'createdAtMonth': serializer.toJson<String?>(createdAtMonth),
|
||||
};
|
||||
|
|
@ -3381,6 +3451,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||
Value<Uint8List?> storedFileHash = const Value.absent(),
|
||||
bool? hasThumbnail,
|
||||
Value<int?> sizeInBytes = const Value.absent(),
|
||||
DateTime? createdAt,
|
||||
Value<String?> createdAtMonth = const Value.absent(),
|
||||
}) => MediaFile(
|
||||
|
|
@ -3421,6 +3493,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
storedFileHash: storedFileHash.present
|
||||
? storedFileHash.value
|
||||
: this.storedFileHash,
|
||||
hasThumbnail: hasThumbnail ?? this.hasThumbnail,
|
||||
sizeInBytes: sizeInBytes.present ? sizeInBytes.value : this.sizeInBytes,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
createdAtMonth: createdAtMonth.present
|
||||
? createdAtMonth.value
|
||||
|
|
@ -3476,6 +3550,12 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
storedFileHash: data.storedFileHash.present
|
||||
? data.storedFileHash.value
|
||||
: this.storedFileHash,
|
||||
hasThumbnail: data.hasThumbnail.present
|
||||
? data.hasThumbnail.value
|
||||
: this.hasThumbnail,
|
||||
sizeInBytes: data.sizeInBytes.present
|
||||
? data.sizeInBytes.value
|
||||
: this.sizeInBytes,
|
||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||
createdAtMonth: data.createdAtMonth.present
|
||||
? data.createdAtMonth.value
|
||||
|
|
@ -3504,6 +3584,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
..write('encryptionMac: $encryptionMac, ')
|
||||
..write('encryptionNonce: $encryptionNonce, ')
|
||||
..write('storedFileHash: $storedFileHash, ')
|
||||
..write('hasThumbnail: $hasThumbnail, ')
|
||||
..write('sizeInBytes: $sizeInBytes, ')
|
||||
..write('createdAt: $createdAt, ')
|
||||
..write('createdAtMonth: $createdAtMonth')
|
||||
..write(')'))
|
||||
|
|
@ -3511,7 +3593,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
int get hashCode => Object.hashAll([
|
||||
mediaId,
|
||||
type,
|
||||
uploadState,
|
||||
|
|
@ -3530,9 +3612,11 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
$driftBlobEquality.hash(encryptionMac),
|
||||
$driftBlobEquality.hash(encryptionNonce),
|
||||
$driftBlobEquality.hash(storedFileHash),
|
||||
hasThumbnail,
|
||||
sizeInBytes,
|
||||
createdAt,
|
||||
createdAtMonth,
|
||||
);
|
||||
]);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
|
|
@ -3561,6 +3645,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
other.storedFileHash,
|
||||
this.storedFileHash,
|
||||
) &&
|
||||
other.hasThumbnail == this.hasThumbnail &&
|
||||
other.sizeInBytes == this.sizeInBytes &&
|
||||
other.createdAt == this.createdAt &&
|
||||
other.createdAtMonth == this.createdAtMonth);
|
||||
}
|
||||
|
|
@ -3584,6 +3670,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
final Value<Uint8List?> encryptionMac;
|
||||
final Value<Uint8List?> encryptionNonce;
|
||||
final Value<Uint8List?> storedFileHash;
|
||||
final Value<bool> hasThumbnail;
|
||||
final Value<int?> sizeInBytes;
|
||||
final Value<DateTime> createdAt;
|
||||
final Value<String?> createdAtMonth;
|
||||
final Value<int> rowid;
|
||||
|
|
@ -3606,6 +3694,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
this.encryptionMac = const Value.absent(),
|
||||
this.encryptionNonce = const Value.absent(),
|
||||
this.storedFileHash = const Value.absent(),
|
||||
this.hasThumbnail = const Value.absent(),
|
||||
this.sizeInBytes = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
this.createdAtMonth = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
|
|
@ -3629,6 +3719,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
this.encryptionMac = const Value.absent(),
|
||||
this.encryptionNonce = const Value.absent(),
|
||||
this.storedFileHash = const Value.absent(),
|
||||
this.hasThumbnail = const Value.absent(),
|
||||
this.sizeInBytes = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
this.createdAtMonth = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
|
|
@ -3653,6 +3745,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
Expression<Uint8List>? encryptionMac,
|
||||
Expression<Uint8List>? encryptionNonce,
|
||||
Expression<Uint8List>? storedFileHash,
|
||||
Expression<bool>? hasThumbnail,
|
||||
Expression<int>? sizeInBytes,
|
||||
Expression<DateTime>? createdAt,
|
||||
Expression<String>? createdAtMonth,
|
||||
Expression<int>? rowid,
|
||||
|
|
@ -3680,6 +3774,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
if (encryptionMac != null) 'encryption_mac': encryptionMac,
|
||||
if (encryptionNonce != null) 'encryption_nonce': encryptionNonce,
|
||||
if (storedFileHash != null) 'stored_file_hash': storedFileHash,
|
||||
if (hasThumbnail != null) 'has_thumbnail': hasThumbnail,
|
||||
if (sizeInBytes != null) 'size_in_bytes': sizeInBytes,
|
||||
if (createdAt != null) 'created_at': createdAt,
|
||||
if (createdAtMonth != null) 'created_at_month': createdAtMonth,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
|
|
@ -3705,6 +3801,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
Value<Uint8List?>? encryptionMac,
|
||||
Value<Uint8List?>? encryptionNonce,
|
||||
Value<Uint8List?>? storedFileHash,
|
||||
Value<bool>? hasThumbnail,
|
||||
Value<int?>? sizeInBytes,
|
||||
Value<DateTime>? createdAt,
|
||||
Value<String?>? createdAtMonth,
|
||||
Value<int>? rowid,
|
||||
|
|
@ -3731,6 +3829,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
encryptionMac: encryptionMac ?? this.encryptionMac,
|
||||
encryptionNonce: encryptionNonce ?? this.encryptionNonce,
|
||||
storedFileHash: storedFileHash ?? this.storedFileHash,
|
||||
hasThumbnail: hasThumbnail ?? this.hasThumbnail,
|
||||
sizeInBytes: sizeInBytes ?? this.sizeInBytes,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
createdAtMonth: createdAtMonth ?? this.createdAtMonth,
|
||||
rowid: rowid ?? this.rowid,
|
||||
|
|
@ -3810,6 +3910,12 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
if (storedFileHash.present) {
|
||||
map['stored_file_hash'] = Variable<Uint8List>(storedFileHash.value);
|
||||
}
|
||||
if (hasThumbnail.present) {
|
||||
map['has_thumbnail'] = Variable<bool>(hasThumbnail.value);
|
||||
}
|
||||
if (sizeInBytes.present) {
|
||||
map['size_in_bytes'] = Variable<int>(sizeInBytes.value);
|
||||
}
|
||||
if (createdAt.present) {
|
||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||
}
|
||||
|
|
@ -3843,6 +3949,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
..write('encryptionMac: $encryptionMac, ')
|
||||
..write('encryptionNonce: $encryptionNonce, ')
|
||||
..write('storedFileHash: $storedFileHash, ')
|
||||
..write('hasThumbnail: $hasThumbnail, ')
|
||||
..write('sizeInBytes: $sizeInBytes, ')
|
||||
..write('createdAt: $createdAt, ')
|
||||
..write('createdAtMonth: $createdAtMonth, ')
|
||||
..write('rowid: $rowid')
|
||||
|
|
@ -15344,6 +15452,8 @@ typedef $$MediaFilesTableCreateCompanionBuilder =
|
|||
Value<Uint8List?> encryptionMac,
|
||||
Value<Uint8List?> encryptionNonce,
|
||||
Value<Uint8List?> storedFileHash,
|
||||
Value<bool> hasThumbnail,
|
||||
Value<int?> sizeInBytes,
|
||||
Value<DateTime> createdAt,
|
||||
Value<String?> createdAtMonth,
|
||||
Value<int> rowid,
|
||||
|
|
@ -15368,6 +15478,8 @@ typedef $$MediaFilesTableUpdateCompanionBuilder =
|
|||
Value<Uint8List?> encryptionMac,
|
||||
Value<Uint8List?> encryptionNonce,
|
||||
Value<Uint8List?> storedFileHash,
|
||||
Value<bool> hasThumbnail,
|
||||
Value<int?> sizeInBytes,
|
||||
Value<DateTime> createdAt,
|
||||
Value<String?> createdAtMonth,
|
||||
Value<int> rowid,
|
||||
|
|
@ -15499,6 +15611,16 @@ class $$MediaFilesTableFilterComposer
|
|||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<bool> get hasThumbnail => $composableBuilder(
|
||||
column: $table.hasThumbnail,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<int> get sizeInBytes => $composableBuilder(
|
||||
column: $table.sizeInBytes,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
|
|
@ -15634,6 +15756,16 @@ class $$MediaFilesTableOrderingComposer
|
|||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<bool> get hasThumbnail => $composableBuilder(
|
||||
column: $table.hasThumbnail,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<int> get sizeInBytes => $composableBuilder(
|
||||
column: $table.sizeInBytes,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
|
|
@ -15741,6 +15873,16 @@ class $$MediaFilesTableAnnotationComposer
|
|||
builder: (column) => column,
|
||||
);
|
||||
|
||||
GeneratedColumn<bool> get hasThumbnail => $composableBuilder(
|
||||
column: $table.hasThumbnail,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
GeneratedColumn<int> get sizeInBytes => $composableBuilder(
|
||||
column: $table.sizeInBytes,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
GeneratedColumn<DateTime> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
|
||||
|
|
@ -15821,6 +15963,8 @@ class $$MediaFilesTableTableManager
|
|||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||
Value<Uint8List?> storedFileHash = const Value.absent(),
|
||||
Value<bool> hasThumbnail = const Value.absent(),
|
||||
Value<int?> sizeInBytes = const Value.absent(),
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
Value<String?> createdAtMonth = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
|
|
@ -15843,6 +15987,8 @@ class $$MediaFilesTableTableManager
|
|||
encryptionMac: encryptionMac,
|
||||
encryptionNonce: encryptionNonce,
|
||||
storedFileHash: storedFileHash,
|
||||
hasThumbnail: hasThumbnail,
|
||||
sizeInBytes: sizeInBytes,
|
||||
createdAt: createdAt,
|
||||
createdAtMonth: createdAtMonth,
|
||||
rowid: rowid,
|
||||
|
|
@ -15867,6 +16013,8 @@ class $$MediaFilesTableTableManager
|
|||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||
Value<Uint8List?> storedFileHash = const Value.absent(),
|
||||
Value<bool> hasThumbnail = const Value.absent(),
|
||||
Value<int?> sizeInBytes = const Value.absent(),
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
Value<String?> createdAtMonth = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
|
|
@ -15889,6 +16037,8 @@ class $$MediaFilesTableTableManager
|
|||
encryptionMac: encryptionMac,
|
||||
encryptionNonce: encryptionNonce,
|
||||
storedFileHash: storedFileHash,
|
||||
hasThumbnail: hasThumbnail,
|
||||
sizeInBytes: sizeInBytes,
|
||||
createdAt: createdAt,
|
||||
createdAtMonth: createdAtMonth,
|
||||
rowid: rowid,
|
||||
|
|
|
|||
|
|
@ -8032,6 +8032,519 @@ i1.GeneratedColumn<i2.Uint8List> _column_243(String aliasedName) =>
|
|||
type: i1.DriftSqlType.blob,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
|
||||
final class Schema16 extends i0.VersionedSchema {
|
||||
Schema16({required super.database}) : super(version: 16);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
contacts,
|
||||
groups,
|
||||
mediaFiles,
|
||||
messages,
|
||||
messageHistories,
|
||||
reactions,
|
||||
groupMembers,
|
||||
receipts,
|
||||
receivedReceipts,
|
||||
signalIdentityKeyStores,
|
||||
signalPreKeyStores,
|
||||
signalSenderKeyStores,
|
||||
signalSessionStores,
|
||||
signalSignedPreKeyStores,
|
||||
messageActions,
|
||||
groupHistories,
|
||||
keyVerifications,
|
||||
verificationTokens,
|
||||
userDiscoveryAnnouncedUsers,
|
||||
userDiscoveryUserRelations,
|
||||
userDiscoveryOtherPromotions,
|
||||
userDiscoveryOwnPromotions,
|
||||
userDiscoveryShares,
|
||||
shortcuts,
|
||||
shortcutMembers,
|
||||
];
|
||||
late final Shape39 contacts = Shape39(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'contacts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(user_id)'],
|
||||
columns: [
|
||||
_column_106,
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_109,
|
||||
_column_110,
|
||||
_column_111,
|
||||
_column_112,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_211,
|
||||
_column_212,
|
||||
_column_213,
|
||||
_column_214,
|
||||
_column_215,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape23 groups = Shape23(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(group_id)'],
|
||||
columns: [
|
||||
_column_119,
|
||||
_column_120,
|
||||
_column_121,
|
||||
_column_122,
|
||||
_column_123,
|
||||
_column_124,
|
||||
_column_125,
|
||||
_column_126,
|
||||
_column_127,
|
||||
_column_128,
|
||||
_column_129,
|
||||
_column_130,
|
||||
_column_131,
|
||||
_column_132,
|
||||
_column_133,
|
||||
_column_134,
|
||||
_column_118,
|
||||
_column_135,
|
||||
_column_136,
|
||||
_column_137,
|
||||
_column_138,
|
||||
_column_139,
|
||||
_column_140,
|
||||
_column_141,
|
||||
_column_142,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape51 mediaFiles = Shape51(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'media_files',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(media_id)'],
|
||||
columns: [
|
||||
_column_143,
|
||||
_column_144,
|
||||
_column_145,
|
||||
_column_146,
|
||||
_column_147,
|
||||
_column_148,
|
||||
_column_149,
|
||||
_column_239,
|
||||
_column_240,
|
||||
_column_207,
|
||||
_column_150,
|
||||
_column_151,
|
||||
_column_152,
|
||||
_column_153,
|
||||
_column_154,
|
||||
_column_155,
|
||||
_column_156,
|
||||
_column_157,
|
||||
_column_244,
|
||||
_column_245,
|
||||
_column_118,
|
||||
_column_241,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape25 messages = Shape25(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'messages',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(message_id)'],
|
||||
columns: [
|
||||
_column_158,
|
||||
_column_159,
|
||||
_column_160,
|
||||
_column_144,
|
||||
_column_161,
|
||||
_column_162,
|
||||
_column_163,
|
||||
_column_164,
|
||||
_column_165,
|
||||
_column_153,
|
||||
_column_166,
|
||||
_column_167,
|
||||
_column_168,
|
||||
_column_169,
|
||||
_column_118,
|
||||
_column_170,
|
||||
_column_171,
|
||||
_column_172,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape26 messageHistories = Shape26(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'message_histories',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_173,
|
||||
_column_174,
|
||||
_column_175,
|
||||
_column_161,
|
||||
_column_118,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape27 reactions = Shape27(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'reactions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
|
||||
columns: [_column_174, _column_176, _column_177, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape38 groupMembers = Shape38(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'group_members',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
|
||||
columns: [
|
||||
_column_158,
|
||||
_column_178,
|
||||
_column_179,
|
||||
_column_180,
|
||||
_column_209,
|
||||
_column_210,
|
||||
_column_181,
|
||||
_column_118,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape37 receipts = Shape37(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'receipts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||
columns: [
|
||||
_column_182,
|
||||
_column_183,
|
||||
_column_184,
|
||||
_column_185,
|
||||
_column_186,
|
||||
_column_208,
|
||||
_column_187,
|
||||
_column_188,
|
||||
_column_189,
|
||||
_column_190,
|
||||
_column_191,
|
||||
_column_118,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape30 receivedReceipts = Shape30(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'received_receipts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||
columns: [_column_182, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape31 signalIdentityKeyStores = Shape31(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_identity_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||
columns: [_column_192, _column_193, _column_194, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape32 signalPreKeyStores = Shape32(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_pre_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
|
||||
columns: [_column_195, _column_196, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape11 signalSenderKeyStores = Shape11(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_sender_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
|
||||
columns: [_column_197, _column_198],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape33 signalSessionStores = Shape33(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_session_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||
columns: [_column_192, _column_193, _column_199, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape50 signalSignedPreKeyStores = Shape50(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_signed_pre_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(signed_pre_key_id)'],
|
||||
columns: [_column_242, _column_243, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape34 messageActions = Shape34(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'message_actions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
|
||||
columns: [_column_174, _column_183, _column_144, _column_200],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape35 groupHistories = Shape35(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'group_histories',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(group_history_id)'],
|
||||
columns: [
|
||||
_column_201,
|
||||
_column_158,
|
||||
_column_202,
|
||||
_column_203,
|
||||
_column_204,
|
||||
_column_205,
|
||||
_column_206,
|
||||
_column_144,
|
||||
_column_200,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape40 keyVerifications = Shape40(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'key_verifications',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_216, _column_183, _column_144, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape41 verificationTokens = Shape41(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'verification_tokens',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_217, _column_218, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape42 userDiscoveryAnnouncedUsers = Shape42(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_announced_users',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
|
||||
columns: [
|
||||
_column_219,
|
||||
_column_220,
|
||||
_column_221,
|
||||
_column_222,
|
||||
_column_223,
|
||||
_column_224,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape43 userDiscoveryUserRelations = Shape43(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_user_relations',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(announced_user_id, from_contact_id)'],
|
||||
columns: [_column_225, _column_226, _column_227],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape44 userDiscoveryOtherPromotions = Shape44(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_other_promotions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(from_contact_id, public_id)'],
|
||||
columns: [
|
||||
_column_226,
|
||||
_column_228,
|
||||
_column_229,
|
||||
_column_230,
|
||||
_column_231,
|
||||
_column_227,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape45 userDiscoveryOwnPromotions = Shape45(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_own_promotions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_232, _column_183, _column_233],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape46 userDiscoveryShares = Shape46(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_shares',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_234, _column_235, _column_175],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape47 shortcuts = Shape47(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'shortcuts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_173, _column_236, _column_237],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape48 shortcutMembers = Shape48(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'shortcut_members',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(shortcut_id, group_id)'],
|
||||
columns: [_column_238, _column_158],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
}
|
||||
|
||||
class Shape51 extends i0.VersionedTable {
|
||||
Shape51({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get mediaId =>
|
||||
columnsByName['media_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get type =>
|
||||
columnsByName['type']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get uploadState =>
|
||||
columnsByName['upload_state']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get downloadState =>
|
||||
columnsByName['download_state']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get requiresAuthentication =>
|
||||
columnsByName['requires_authentication']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get stored =>
|
||||
columnsByName['stored']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get isDraftMedia =>
|
||||
columnsByName['is_draft_media']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get isFavorite =>
|
||||
columnsByName['is_favorite']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get hasCropAnalyzed =>
|
||||
columnsByName['has_crop_analyzed']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get preProgressingProcess =>
|
||||
columnsByName['pre_progressing_process']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get reuploadRequestedBy =>
|
||||
columnsByName['reupload_requested_by']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get displayLimitInMilliseconds =>
|
||||
columnsByName['display_limit_in_milliseconds']!
|
||||
as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get removeAudio =>
|
||||
columnsByName['remove_audio']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get downloadToken =>
|
||||
columnsByName['download_token']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get encryptionKey =>
|
||||
columnsByName['encryption_key']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get encryptionMac =>
|
||||
columnsByName['encryption_mac']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get encryptionNonce =>
|
||||
columnsByName['encryption_nonce']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get storedFileHash =>
|
||||
columnsByName['stored_file_hash']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<int> get hasThumbnail =>
|
||||
columnsByName['has_thumbnail']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get sizeInBytes =>
|
||||
columnsByName['size_in_bytes']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get createdAt =>
|
||||
columnsByName['created_at']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get createdAtMonth =>
|
||||
columnsByName['created_at_month']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<int> _column_244(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'has_thumbnail',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints: 'NOT NULL DEFAULT 0 CHECK (has_thumbnail IN (0, 1))',
|
||||
defaultValue: const i1.CustomExpression('0'),
|
||||
);
|
||||
i1.GeneratedColumn<int> _column_245(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'size_in_bytes',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints: 'NULL',
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
|
|
@ -8047,6 +8560,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
||||
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
|
|
@ -8120,6 +8634,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
final migrator = i1.Migrator(database, schema);
|
||||
await from14To15(migrator, schema);
|
||||
return 15;
|
||||
case 15:
|
||||
final schema = Schema16(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from15To16(migrator, schema);
|
||||
return 16;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
|
|
@ -8141,6 +8660,7 @@ i1.OnUpgrade stepByStep({
|
|||
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
||||
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
|
|
@ -8157,5 +8677,6 @@ i1.OnUpgrade stepByStep({
|
|||
from12To13: from12To13,
|
||||
from13To14: from13To14,
|
||||
from14To15: from14To15,
|
||||
from15To16: from15To16,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||
..requestedAudioPermission =
|
||||
json['requestedAudioPermission'] as bool? ?? false
|
||||
..automaticallyMarkEqualMediaFilesAsOpened =
|
||||
json['automaticallyMarkEqualMediaFilesAsOpened'] as bool? ?? false
|
||||
..videoStabilizationEnabled =
|
||||
json['videoStabilizationEnabled'] as bool? ?? true
|
||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||
|
|
@ -121,6 +123,8 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'defaultShowTime': instance.defaultShowTime,
|
||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||
'automaticallyMarkEqualMediaFilesAsOpened':
|
||||
instance.automaticallyMarkEqualMediaFilesAsOpened,
|
||||
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
|
||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
|
||||
|
|
|
|||
|
|
@ -200,14 +200,24 @@ class MediaFileService {
|
|||
Log.error('Could not create Thumbnail as stored media does not exists.');
|
||||
return;
|
||||
}
|
||||
var success = false;
|
||||
switch (mediaFile.type) {
|
||||
case MediaType.gif:
|
||||
case MediaType.audio:
|
||||
success = await createThumbnailsForGif(storedPath, thumbnailPath);
|
||||
case MediaType.image:
|
||||
// all images are already compress..
|
||||
break;
|
||||
success = await createThumbnailsForImage(storedPath, thumbnailPath);
|
||||
case MediaType.video:
|
||||
await createThumbnailsForVideo(storedPath, thumbnailPath);
|
||||
success = await createThumbnailsForVideo(storedPath, thumbnailPath);
|
||||
case MediaType.audio:
|
||||
break;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
const MediaFilesCompanion(hasThumbnail: Value(true)),
|
||||
);
|
||||
await updateFromDB();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +263,9 @@ class MediaFileService {
|
|||
tempPath.existsSync();
|
||||
|
||||
bool get imagePreviewAvailable =>
|
||||
thumbnailPath.existsSync() || storedPath.existsSync();
|
||||
mediaFile.hasThumbnail ||
|
||||
thumbnailPath.existsSync() ||
|
||||
storedPath.existsSync();
|
||||
|
||||
Future<void> storeMediaFile() async {
|
||||
Log.info('Storing media file ${mediaFile.mediaId}');
|
||||
|
|
@ -284,10 +296,24 @@ class MediaFileService {
|
|||
);
|
||||
}
|
||||
unawaited(createThumbnail());
|
||||
await calculateAndSaveSize();
|
||||
await hashMediaFile();
|
||||
// updateFromDb is done in hashStoredMedia()
|
||||
}
|
||||
|
||||
Future<void> calculateAndSaveSize() async {
|
||||
if (storedPath.existsSync()) {
|
||||
final size = storedPath.lengthSync();
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
MediaFilesCompanion(
|
||||
sizeInBytes: Value(size),
|
||||
),
|
||||
);
|
||||
await updateFromDB();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> hashMediaFile() async {
|
||||
late final List<int> checksum;
|
||||
if (storedPath.existsSync()) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:pro_video_editor/pro_video_editor.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
Future<void> createThumbnailsForVideo(
|
||||
Future<bool> createThumbnailsForVideo(
|
||||
File sourceFile,
|
||||
File destinationFile,
|
||||
) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
if (destinationFile.existsSync()) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
final images = await ProVideoEditor.instance.getThumbnails(
|
||||
|
|
@ -28,11 +30,89 @@ Future<void> createThumbnailsForVideo(
|
|||
stopwatch.stop();
|
||||
destinationFile.writeAsBytesSync(images.first);
|
||||
Log.info(
|
||||
'It took ${stopwatch.elapsedMilliseconds}ms to create the thumbnail.',
|
||||
'It took ${stopwatch.elapsedMilliseconds}ms to create the video thumbnail.',
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
Log.warn(
|
||||
'Thumbnail creation failed for the video with exit code.',
|
||||
'Thumbnail creation failed for the video.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> createThumbnailsForImage(
|
||||
File sourceFile,
|
||||
File destinationFile,
|
||||
) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
if (destinationFile.existsSync()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
final bytes = sourceFile.readAsBytesSync();
|
||||
final result = await FlutterImageCompress.compressWithList(
|
||||
bytes,
|
||||
minWidth: 200,
|
||||
minHeight: 200,
|
||||
quality: 70,
|
||||
format: CompressFormat.webp,
|
||||
);
|
||||
|
||||
destinationFile.writeAsBytesSync(result);
|
||||
stopwatch.stop();
|
||||
Log.info(
|
||||
'It took ${stopwatch.elapsedMilliseconds}ms to create the image thumbnail.',
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error('Error creating image thumbnail: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> createThumbnailsForGif(
|
||||
File sourceFile,
|
||||
File destinationFile,
|
||||
) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
if (destinationFile.existsSync()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// For GIFs, we decode the first frame and save it as WebP
|
||||
final bytes = sourceFile.readAsBytesSync();
|
||||
final image = img.decodeGif(bytes);
|
||||
if (image == null) {
|
||||
Log.error('Could not decode GIF for thumbnail.');
|
||||
return false;
|
||||
}
|
||||
|
||||
final thumbnail = img.copyResize(
|
||||
image,
|
||||
width: image.width > image.height ? 200 : null,
|
||||
height: image.height >= image.width ? 200 : null,
|
||||
);
|
||||
|
||||
final pngBytes = img.encodePng(thumbnail);
|
||||
final webp = await FlutterImageCompress.compressWithList(
|
||||
pngBytes,
|
||||
format: CompressFormat.webp,
|
||||
quality: 70,
|
||||
);
|
||||
destinationFile.writeAsBytesSync(webp);
|
||||
|
||||
stopwatch.stop();
|
||||
Log.info(
|
||||
'It took ${stopwatch.elapsedMilliseconds}ms to create the GIF thumbnail.',
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error('Error creating GIF thumbnail: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
|
|
@ -170,25 +171,38 @@ class MemoriesService {
|
|||
|
||||
Future<void> _initAsync() async {
|
||||
try {
|
||||
// 1. Perform Inventory / Migration of non-hashed stored files
|
||||
final nonHashedFiles = await twonlyDB.mediaFilesDao
|
||||
.getAllNonHashedStoredMediaFiles();
|
||||
final unanalyzedFiles = await twonlyDB.mediaFilesDao
|
||||
.getAllUnanalyzedStoredMediaFiles();
|
||||
// 1. Perform Inventory / Migration of stored files
|
||||
final pendingFiles = await twonlyDB.mediaFilesDao
|
||||
.getAllMediaFilesPendingMigration();
|
||||
|
||||
final totalToMigrate = nonHashedFiles.length + unanalyzedFiles.length;
|
||||
if (totalToMigrate > 0) {
|
||||
_updateState(filesToMigrate: totalToMigrate);
|
||||
if (pendingFiles.isNotEmpty) {
|
||||
_updateState(filesToMigrate: pendingFiles.length);
|
||||
|
||||
for (final mediaFile in nonHashedFiles) {
|
||||
for (final mediaFile in pendingFiles) {
|
||||
final mediaService = MediaFileService(mediaFile);
|
||||
|
||||
if (mediaService.mediaFile.storedFileHash == null) {
|
||||
await mediaService.hashMediaFile();
|
||||
_updateState(filesToMigrate: _currentState.filesToMigrate - 1);
|
||||
}
|
||||
|
||||
for (final mediaFile in unanalyzedFiles) {
|
||||
final mediaService = MediaFileService(mediaFile);
|
||||
if (!mediaService.mediaFile.hasCropAnalyzed) {
|
||||
await mediaService.cropTransparentBorders();
|
||||
}
|
||||
|
||||
if (mediaService.mediaFile.sizeInBytes == null) {
|
||||
await mediaService.calculateAndSaveSize();
|
||||
}
|
||||
|
||||
if (!mediaService.mediaFile.hasThumbnail) {
|
||||
if (mediaService.thumbnailPath.existsSync()) {
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
const MediaFilesCompanion(hasThumbnail: Value(true)),
|
||||
);
|
||||
} else if (mediaFile.type != MediaType.audio) {
|
||||
await mediaService.createThumbnail();
|
||||
}
|
||||
}
|
||||
_updateState(filesToMigrate: _currentState.filesToMigrate - 1);
|
||||
}
|
||||
|
||||
|
|
@ -234,11 +248,10 @@ class MemoriesService {
|
|||
final mediaService = MediaFileService(mediaFile);
|
||||
if (!mediaService.imagePreviewAvailable) continue;
|
||||
|
||||
if (mediaService.mediaFile.type == MediaType.video) {
|
||||
if (!mediaService.thumbnailPath.existsSync()) {
|
||||
if (!mediaService.mediaFile.hasThumbnail &&
|
||||
mediaService.mediaFile.type != MediaType.audio) {
|
||||
unawaited(mediaService.createThumbnail());
|
||||
}
|
||||
}
|
||||
|
||||
final senderContact = mediaIdToSenderContact[mediaFile.mediaId];
|
||||
final item = MemoryItem(
|
||||
|
|
|
|||
342
lib/src/visual/components/draggable_scrollbar.comp.dart
Normal file
342
lib/src/visual/components/draggable_scrollbar.comp.dart
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
typedef TextLabelBuilder = Widget Function(String label);
|
||||
|
||||
class DraggableScrollbar extends StatefulWidget {
|
||||
const DraggableScrollbar({
|
||||
required this.controller,
|
||||
required this.child,
|
||||
this.labelBuilder,
|
||||
super.key,
|
||||
});
|
||||
final ScrollController controller;
|
||||
final Widget child;
|
||||
final String? Function(double scrollOffset)? labelBuilder;
|
||||
|
||||
@override
|
||||
State<DraggableScrollbar> createState() => _DraggableScrollbarState();
|
||||
|
||||
static const double labelThumbPadding = 16;
|
||||
}
|
||||
|
||||
class _DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
with TickerProviderStateMixin {
|
||||
final ValueNotifier<double> _thumbOffsetNotifier = ValueNotifier(0);
|
||||
final ValueNotifier<double> _viewOffsetNotifier = ValueNotifier(0);
|
||||
bool _isDragInProcess = false;
|
||||
double _boundlessThumbOffset = 0;
|
||||
|
||||
late AnimationController _thumbAnimationController;
|
||||
late CurvedAnimation _thumbAnimation;
|
||||
late AnimationController _labelAnimationController;
|
||||
late CurvedAnimation _labelAnimation;
|
||||
Timer? _fadeoutTimer;
|
||||
|
||||
static const double thumbHeight = 60;
|
||||
static const double thumbWidth = 20;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_thumbAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
|
||||
_thumbAnimation = CurvedAnimation(
|
||||
parent: _thumbAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
|
||||
_labelAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
|
||||
_labelAnimation = CurvedAnimation(
|
||||
parent: _labelAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_thumbOffsetNotifier.dispose();
|
||||
_viewOffsetNotifier.dispose();
|
||||
_thumbAnimation.dispose();
|
||||
_thumbAnimationController.dispose();
|
||||
_labelAnimation.dispose();
|
||||
_labelAnimationController.dispose();
|
||||
_fadeoutTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
ScrollController get controller => widget.controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
_onScrollNotification(notification);
|
||||
return false;
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
RepaintBoundary(
|
||||
child: widget.child,
|
||||
),
|
||||
// Scrollbar layer restricted to SafeArea
|
||||
SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxThumbOffset = constraints.maxHeight - thumbHeight;
|
||||
|
||||
return ExcludeSemantics(
|
||||
child: RepaintBoundary(
|
||||
child: ValueListenableBuilder<double>(
|
||||
valueListenable: _thumbOffsetNotifier,
|
||||
builder: (context, thumbOffset, child) {
|
||||
final isDark =
|
||||
Theme.of(context).brightness == Brightness.dark;
|
||||
final handleColor = isDark
|
||||
? Colors.grey.shade900
|
||||
: Colors.white;
|
||||
final iconColor = isDark
|
||||
? Colors.white70
|
||||
: Colors.black54;
|
||||
|
||||
final label = widget.labelBuilder?.call(
|
||||
_viewOffsetNotifier.value,
|
||||
);
|
||||
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
padding: EdgeInsets.only(top: thumbOffset),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onVerticalDragStart: (_) => _onVerticalDragStart(),
|
||||
onVerticalDragUpdate: (details) =>
|
||||
_onVerticalDragUpdate(
|
||||
details.delta.dy,
|
||||
maxThumbOffset,
|
||||
),
|
||||
onVerticalDragEnd: (_) => _onVerticalDragEnd(),
|
||||
child: SlideFadeTransition(
|
||||
animation: _thumbAnimation,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (label != null && _isDragInProcess)
|
||||
FadeTransition(
|
||||
opacity: _labelAnimation,
|
||||
child: ScaleTransition(
|
||||
scale: _labelAnimation,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(
|
||||
right: DraggableScrollbar
|
||||
.labelThumbPadding,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 6,
|
||||
horizontal: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
(isDark
|
||||
? Colors.grey.shade900
|
||||
: Colors.grey.shade200)
|
||||
.withValues(alpha: 0.95),
|
||||
borderRadius: BorderRadius.circular(
|
||||
8,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: thumbWidth,
|
||||
height: thumbHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: handleColor,
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white10
|
||||
: Colors.black12,
|
||||
width: 0.5,
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.angleUp,
|
||||
size: 14,
|
||||
color: iconColor,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 4,
|
||||
),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.angleDown,
|
||||
size: 14,
|
||||
color: iconColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onScrollNotification(ScrollNotification notification) {
|
||||
final scrollMetrics = notification.metrics;
|
||||
if (scrollMetrics.minScrollExtent >= scrollMetrics.maxScrollExtent) return;
|
||||
|
||||
_viewOffsetNotifier.value = scrollMetrics.pixels;
|
||||
|
||||
if (!_isDragInProcess) {
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
// Find constraints and update thumb offset
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
final renderBox = context.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) return;
|
||||
|
||||
// Subtract SafeArea top/bottom
|
||||
final padding = MediaQuery.paddingOf(context);
|
||||
final availableHeight = renderBox.size.height - padding.vertical;
|
||||
final maxThumbOffset = availableHeight - thumbHeight;
|
||||
|
||||
final scrollExtent =
|
||||
scrollMetrics.pixels /
|
||||
scrollMetrics.maxScrollExtent *
|
||||
maxThumbOffset;
|
||||
_thumbOffsetNotifier.value = scrollExtent.clamp(0.0, maxThumbOffset);
|
||||
});
|
||||
}
|
||||
|
||||
if (notification is ScrollUpdateNotification ||
|
||||
notification is OverscrollNotification) {
|
||||
_showThumb();
|
||||
_scheduleFadeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onVerticalDragStart() {
|
||||
_boundlessThumbOffset = _thumbOffsetNotifier.value;
|
||||
_labelAnimationController.forward();
|
||||
_fadeoutTimer?.cancel();
|
||||
_showThumb();
|
||||
setState(() => _isDragInProcess = true);
|
||||
}
|
||||
|
||||
void _onVerticalDragUpdate(double deltaY, double maxThumbOffset) {
|
||||
_showThumb();
|
||||
if (_isDragInProcess && maxThumbOffset > 0) {
|
||||
_boundlessThumbOffset += deltaY;
|
||||
_thumbOffsetNotifier.value = _boundlessThumbOffset.clamp(
|
||||
0.0,
|
||||
maxThumbOffset,
|
||||
);
|
||||
|
||||
final max = controller.position.maxScrollExtent;
|
||||
final scrollOffset = (_thumbOffsetNotifier.value / maxThumbOffset) * max;
|
||||
controller.jumpTo(
|
||||
scrollOffset.clamp(0.0, controller.position.maxScrollExtent),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onVerticalDragEnd() {
|
||||
_scheduleFadeout();
|
||||
setState(() => _isDragInProcess = false);
|
||||
}
|
||||
|
||||
void _showThumb() {
|
||||
if (_thumbAnimationController.status != AnimationStatus.forward) {
|
||||
_thumbAnimationController.forward();
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleFadeout() {
|
||||
_fadeoutTimer?.cancel();
|
||||
_fadeoutTimer = Timer(const Duration(milliseconds: 1500), () {
|
||||
_thumbAnimationController.reverse();
|
||||
_labelAnimationController.reverse();
|
||||
_fadeoutTimer = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SlideFadeTransition extends StatelessWidget {
|
||||
const SlideFadeTransition({
|
||||
required this.animation,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
final Animation<double> animation;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: animation.value,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -286,15 +286,14 @@ class HomeViewState extends State<HomeView> {
|
|||
bottomNavigationBar: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
child: _isBottomNavVisible
|
||||
child: (_activePageIdx != 2 || _isBottomNavVisible)
|
||||
? BottomNavigationBar(
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
unselectedIconTheme: IconThemeData(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.inverseSurface
|
||||
.withAlpha(150),
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.inverseSurface.withAlpha(150),
|
||||
),
|
||||
selectedIconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.inverseSurface,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:twonly/src/model/memory_item.model.dart';
|
|||
import 'package:twonly/src/services/memories/memories.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/components/draggable_scrollbar.comp.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart';
|
||||
import 'package:twonly/src/visual/views/memories/components/flashback_banner.comp.dart';
|
||||
|
|
@ -371,11 +372,37 @@ class MemoriesViewState extends State<MemoriesView> {
|
|||
orderedByMonth = filteredOrdered;
|
||||
}
|
||||
|
||||
return Scrollbar(
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return DraggableScrollbar(
|
||||
controller: _scrollController,
|
||||
thickness: 12,
|
||||
radius: const Radius.circular(6),
|
||||
interactive: true,
|
||||
labelBuilder: (offset) {
|
||||
final state = _service.currentState;
|
||||
if (state.isEmpty) return null;
|
||||
|
||||
// Simple heuristic to find month by offset
|
||||
double currentOffset = 56;
|
||||
if (state.galleryItemsLastYears.isNotEmpty) {
|
||||
currentOffset += 220;
|
||||
}
|
||||
|
||||
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final itemWidth = (screenWidth - 8) / 4;
|
||||
final itemHeight = itemWidth * (16 / 9);
|
||||
final rowHeight = itemHeight + 2;
|
||||
|
||||
for (final month in state.months) {
|
||||
final indices = state.orderedByMonth[month]!;
|
||||
final totalRows = (indices.length + 3) ~/ 4;
|
||||
final monthHeight = 44 + (totalRows * rowHeight);
|
||||
|
||||
if (offset < currentOffset + monthHeight) {
|
||||
return month;
|
||||
}
|
||||
currentOffset += monthHeight;
|
||||
}
|
||||
return state.months.last;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
|
|
@ -410,13 +437,11 @@ class MemoriesViewState extends State<MemoriesView> {
|
|||
),
|
||||
],
|
||||
),
|
||||
|
||||
MemoriesFlashbackBannerComp(
|
||||
lastYears: lastYears,
|
||||
onOpenFlashback: (items, idx) =>
|
||||
_openViewer(items, idx, isFlashback: true),
|
||||
),
|
||||
|
||||
for (final month in months) ...[
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 12, 8, 6),
|
||||
|
|
@ -442,7 +467,8 @@ class MemoriesViewState extends State<MemoriesView> {
|
|||
(context, idx) {
|
||||
final globalIndex = orderedByMonth[month]![idx];
|
||||
final item = state.galleryItems[globalIndex];
|
||||
final mediaId = item.mediaService.mediaFile.mediaId;
|
||||
final mediaId =
|
||||
item.mediaService.mediaFile.mediaId;
|
||||
final isSelected = _selectedMediaIds.contains(
|
||||
mediaId,
|
||||
);
|
||||
|
|
@ -461,11 +487,15 @@ class MemoriesViewState extends State<MemoriesView> {
|
|||
),
|
||||
),
|
||||
],
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: 32)),
|
||||
const SliverPadding(
|
||||
padding: EdgeInsets.only(bottom: 32),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
if (_selectionMode)
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
|
|||
isDraftMedia: false,
|
||||
isFavorite: false,
|
||||
hasCropAnalyzed: false,
|
||||
hasThumbnail: false,
|
||||
createdAt: now,
|
||||
);
|
||||
final mediaService = MediaFileService(mediaFile);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'schema_v12.dart' as v12;
|
|||
import 'schema_v13.dart' as v13;
|
||||
import 'schema_v14.dart' as v14;
|
||||
import 'schema_v15.dart' as v15;
|
||||
import 'schema_v16.dart' as v16;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
|
|
@ -54,6 +55,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
return v14.DatabaseAtV14(db);
|
||||
case 15:
|
||||
return v15.DatabaseAtV15(db);
|
||||
case 16:
|
||||
return v16.DatabaseAtV16(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
|
|
@ -75,5 +78,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
10584
test/drift/twonly_db/generated/schema_v16.dart
Normal file
10584
test/drift/twonly_db/generated/schema_v16.dart
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue