mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 07:02:12 +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
|
# Changelog
|
||||||
|
|
||||||
|
## 0.2.13
|
||||||
|
|
||||||
|
- Improved: Media thumbnails for faster loading
|
||||||
|
|
||||||
## 0.2.12
|
## 0.2.12
|
||||||
|
|
||||||
- New: Automatically mark identical media as opened across all chats (Settings > Chats).
|
- New: Automatically mark identical media as opened across all chats (Settings > Chats).
|
||||||
|
|
|
||||||
|
|
@ -114,16 +114,15 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getAllNonHashedStoredMediaFiles() async {
|
Future<List<MediaFile>> getAllMediaFilesPendingMigration() async {
|
||||||
return (select(mediaFiles)..where(
|
return (select(mediaFiles)..where(
|
||||||
(t) => t.stored.equals(true) & t.storedFileHash.isNull(),
|
(t) =>
|
||||||
))
|
t.stored.equals(true) &
|
||||||
.get();
|
(t.storedFileHash.isNull() |
|
||||||
}
|
t.hasCropAnalyzed.equals(false) |
|
||||||
|
(t.hasThumbnail.equals(false) &
|
||||||
Future<List<MediaFile>> getAllUnanalyzedStoredMediaFiles() async {
|
t.type.equals(MediaType.audio.name).not()) |
|
||||||
return (select(mediaFiles)..where(
|
t.sizeInBytes.isNull()),
|
||||||
(t) => t.stored.equals(true) & t.hasCropAnalyzed.equals(false),
|
|
||||||
))
|
))
|
||||||
.get();
|
.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()();
|
BlobColumn get storedFileHash => blob().nullable()();
|
||||||
|
|
||||||
|
BoolColumn get hasThumbnail =>
|
||||||
|
boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
|
IntColumn get sizeInBytes => integer().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
TextColumn get createdAtMonth => text().nullable()();
|
TextColumn get createdAtMonth => text().nullable()();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 15;
|
int get schemaVersion => 16;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
|
|
@ -211,6 +211,13 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
from14To15: (m, schema) async {
|
from14To15: (m, schema) async {
|
||||||
await m.createTable(schema.signalSignedPreKeyStores);
|
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);
|
)(m, from, to);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2810,6 +2810,32 @@ class $MediaFilesTable extends MediaFiles
|
||||||
type: DriftSqlType.blob,
|
type: DriftSqlType.blob,
|
||||||
requiredDuringInsert: false,
|
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(
|
static const VerificationMeta _createdAtMeta = const VerificationMeta(
|
||||||
'createdAt',
|
'createdAt',
|
||||||
);
|
);
|
||||||
|
|
@ -2853,6 +2879,8 @@ class $MediaFilesTable extends MediaFiles
|
||||||
encryptionMac,
|
encryptionMac,
|
||||||
encryptionNonce,
|
encryptionNonce,
|
||||||
storedFileHash,
|
storedFileHash,
|
||||||
|
hasThumbnail,
|
||||||
|
sizeInBytes,
|
||||||
createdAt,
|
createdAt,
|
||||||
createdAtMonth,
|
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')) {
|
if (data.containsKey('created_at')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_createdAtMeta,
|
_createdAtMeta,
|
||||||
|
|
@ -3092,6 +3138,14 @@ class $MediaFilesTable extends MediaFiles
|
||||||
DriftSqlType.blob,
|
DriftSqlType.blob,
|
||||||
data['${effectivePrefix}stored_file_hash'],
|
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(
|
createdAt: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.dateTime,
|
DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}created_at'],
|
data['${effectivePrefix}created_at'],
|
||||||
|
|
@ -3147,6 +3201,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
final Uint8List? encryptionMac;
|
final Uint8List? encryptionMac;
|
||||||
final Uint8List? encryptionNonce;
|
final Uint8List? encryptionNonce;
|
||||||
final Uint8List? storedFileHash;
|
final Uint8List? storedFileHash;
|
||||||
|
final bool hasThumbnail;
|
||||||
|
final int? sizeInBytes;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final String? createdAtMonth;
|
final String? createdAtMonth;
|
||||||
const MediaFile({
|
const MediaFile({
|
||||||
|
|
@ -3168,6 +3224,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
this.encryptionMac,
|
this.encryptionMac,
|
||||||
this.encryptionNonce,
|
this.encryptionNonce,
|
||||||
this.storedFileHash,
|
this.storedFileHash,
|
||||||
|
required this.hasThumbnail,
|
||||||
|
this.sizeInBytes,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
this.createdAtMonth,
|
this.createdAtMonth,
|
||||||
});
|
});
|
||||||
|
|
@ -3228,6 +3286,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
if (!nullToAbsent || storedFileHash != null) {
|
if (!nullToAbsent || storedFileHash != null) {
|
||||||
map['stored_file_hash'] = Variable<Uint8List>(storedFileHash);
|
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);
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
if (!nullToAbsent || createdAtMonth != null) {
|
if (!nullToAbsent || createdAtMonth != null) {
|
||||||
map['created_at_month'] = Variable<String>(createdAtMonth);
|
map['created_at_month'] = Variable<String>(createdAtMonth);
|
||||||
|
|
@ -3278,6 +3340,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
storedFileHash: storedFileHash == null && nullToAbsent
|
storedFileHash: storedFileHash == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(storedFileHash),
|
: Value(storedFileHash),
|
||||||
|
hasThumbnail: Value(hasThumbnail),
|
||||||
|
sizeInBytes: sizeInBytes == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(sizeInBytes),
|
||||||
createdAt: Value(createdAt),
|
createdAt: Value(createdAt),
|
||||||
createdAtMonth: createdAtMonth == null && nullToAbsent
|
createdAtMonth: createdAtMonth == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
|
|
@ -3323,6 +3389,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
encryptionMac: serializer.fromJson<Uint8List?>(json['encryptionMac']),
|
encryptionMac: serializer.fromJson<Uint8List?>(json['encryptionMac']),
|
||||||
encryptionNonce: serializer.fromJson<Uint8List?>(json['encryptionNonce']),
|
encryptionNonce: serializer.fromJson<Uint8List?>(json['encryptionNonce']),
|
||||||
storedFileHash: serializer.fromJson<Uint8List?>(json['storedFileHash']),
|
storedFileHash: serializer.fromJson<Uint8List?>(json['storedFileHash']),
|
||||||
|
hasThumbnail: serializer.fromJson<bool>(json['hasThumbnail']),
|
||||||
|
sizeInBytes: serializer.fromJson<int?>(json['sizeInBytes']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
createdAtMonth: serializer.fromJson<String?>(json['createdAtMonth']),
|
createdAtMonth: serializer.fromJson<String?>(json['createdAtMonth']),
|
||||||
);
|
);
|
||||||
|
|
@ -3357,6 +3425,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
'encryptionMac': serializer.toJson<Uint8List?>(encryptionMac),
|
'encryptionMac': serializer.toJson<Uint8List?>(encryptionMac),
|
||||||
'encryptionNonce': serializer.toJson<Uint8List?>(encryptionNonce),
|
'encryptionNonce': serializer.toJson<Uint8List?>(encryptionNonce),
|
||||||
'storedFileHash': serializer.toJson<Uint8List?>(storedFileHash),
|
'storedFileHash': serializer.toJson<Uint8List?>(storedFileHash),
|
||||||
|
'hasThumbnail': serializer.toJson<bool>(hasThumbnail),
|
||||||
|
'sizeInBytes': serializer.toJson<int?>(sizeInBytes),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
'createdAtMonth': serializer.toJson<String?>(createdAtMonth),
|
'createdAtMonth': serializer.toJson<String?>(createdAtMonth),
|
||||||
};
|
};
|
||||||
|
|
@ -3381,6 +3451,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||||
Value<Uint8List?> storedFileHash = const Value.absent(),
|
Value<Uint8List?> storedFileHash = const Value.absent(),
|
||||||
|
bool? hasThumbnail,
|
||||||
|
Value<int?> sizeInBytes = const Value.absent(),
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
Value<String?> createdAtMonth = const Value.absent(),
|
Value<String?> createdAtMonth = const Value.absent(),
|
||||||
}) => MediaFile(
|
}) => MediaFile(
|
||||||
|
|
@ -3421,6 +3493,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
storedFileHash: storedFileHash.present
|
storedFileHash: storedFileHash.present
|
||||||
? storedFileHash.value
|
? storedFileHash.value
|
||||||
: this.storedFileHash,
|
: this.storedFileHash,
|
||||||
|
hasThumbnail: hasThumbnail ?? this.hasThumbnail,
|
||||||
|
sizeInBytes: sizeInBytes.present ? sizeInBytes.value : this.sizeInBytes,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
createdAtMonth: createdAtMonth.present
|
createdAtMonth: createdAtMonth.present
|
||||||
? createdAtMonth.value
|
? createdAtMonth.value
|
||||||
|
|
@ -3476,6 +3550,12 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
storedFileHash: data.storedFileHash.present
|
storedFileHash: data.storedFileHash.present
|
||||||
? data.storedFileHash.value
|
? data.storedFileHash.value
|
||||||
: this.storedFileHash,
|
: 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,
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
createdAtMonth: data.createdAtMonth.present
|
createdAtMonth: data.createdAtMonth.present
|
||||||
? data.createdAtMonth.value
|
? data.createdAtMonth.value
|
||||||
|
|
@ -3504,6 +3584,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
..write('encryptionMac: $encryptionMac, ')
|
..write('encryptionMac: $encryptionMac, ')
|
||||||
..write('encryptionNonce: $encryptionNonce, ')
|
..write('encryptionNonce: $encryptionNonce, ')
|
||||||
..write('storedFileHash: $storedFileHash, ')
|
..write('storedFileHash: $storedFileHash, ')
|
||||||
|
..write('hasThumbnail: $hasThumbnail, ')
|
||||||
|
..write('sizeInBytes: $sizeInBytes, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('createdAtMonth: $createdAtMonth')
|
..write('createdAtMonth: $createdAtMonth')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
|
|
@ -3511,7 +3593,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hashAll([
|
||||||
mediaId,
|
mediaId,
|
||||||
type,
|
type,
|
||||||
uploadState,
|
uploadState,
|
||||||
|
|
@ -3530,9 +3612,11 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
$driftBlobEquality.hash(encryptionMac),
|
$driftBlobEquality.hash(encryptionMac),
|
||||||
$driftBlobEquality.hash(encryptionNonce),
|
$driftBlobEquality.hash(encryptionNonce),
|
||||||
$driftBlobEquality.hash(storedFileHash),
|
$driftBlobEquality.hash(storedFileHash),
|
||||||
|
hasThumbnail,
|
||||||
|
sizeInBytes,
|
||||||
createdAt,
|
createdAt,
|
||||||
createdAtMonth,
|
createdAtMonth,
|
||||||
);
|
]);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
|
@ -3561,6 +3645,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
other.storedFileHash,
|
other.storedFileHash,
|
||||||
this.storedFileHash,
|
this.storedFileHash,
|
||||||
) &&
|
) &&
|
||||||
|
other.hasThumbnail == this.hasThumbnail &&
|
||||||
|
other.sizeInBytes == this.sizeInBytes &&
|
||||||
other.createdAt == this.createdAt &&
|
other.createdAt == this.createdAt &&
|
||||||
other.createdAtMonth == this.createdAtMonth);
|
other.createdAtMonth == this.createdAtMonth);
|
||||||
}
|
}
|
||||||
|
|
@ -3584,6 +3670,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
final Value<Uint8List?> encryptionMac;
|
final Value<Uint8List?> encryptionMac;
|
||||||
final Value<Uint8List?> encryptionNonce;
|
final Value<Uint8List?> encryptionNonce;
|
||||||
final Value<Uint8List?> storedFileHash;
|
final Value<Uint8List?> storedFileHash;
|
||||||
|
final Value<bool> hasThumbnail;
|
||||||
|
final Value<int?> sizeInBytes;
|
||||||
final Value<DateTime> createdAt;
|
final Value<DateTime> createdAt;
|
||||||
final Value<String?> createdAtMonth;
|
final Value<String?> createdAtMonth;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
|
|
@ -3606,6 +3694,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
this.encryptionMac = const Value.absent(),
|
this.encryptionMac = const Value.absent(),
|
||||||
this.encryptionNonce = const Value.absent(),
|
this.encryptionNonce = const Value.absent(),
|
||||||
this.storedFileHash = const Value.absent(),
|
this.storedFileHash = const Value.absent(),
|
||||||
|
this.hasThumbnail = const Value.absent(),
|
||||||
|
this.sizeInBytes = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.createdAtMonth = const Value.absent(),
|
this.createdAtMonth = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
|
|
@ -3629,6 +3719,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
this.encryptionMac = const Value.absent(),
|
this.encryptionMac = const Value.absent(),
|
||||||
this.encryptionNonce = const Value.absent(),
|
this.encryptionNonce = const Value.absent(),
|
||||||
this.storedFileHash = const Value.absent(),
|
this.storedFileHash = const Value.absent(),
|
||||||
|
this.hasThumbnail = const Value.absent(),
|
||||||
|
this.sizeInBytes = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.createdAtMonth = const Value.absent(),
|
this.createdAtMonth = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
|
|
@ -3653,6 +3745,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
Expression<Uint8List>? encryptionMac,
|
Expression<Uint8List>? encryptionMac,
|
||||||
Expression<Uint8List>? encryptionNonce,
|
Expression<Uint8List>? encryptionNonce,
|
||||||
Expression<Uint8List>? storedFileHash,
|
Expression<Uint8List>? storedFileHash,
|
||||||
|
Expression<bool>? hasThumbnail,
|
||||||
|
Expression<int>? sizeInBytes,
|
||||||
Expression<DateTime>? createdAt,
|
Expression<DateTime>? createdAt,
|
||||||
Expression<String>? createdAtMonth,
|
Expression<String>? createdAtMonth,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
|
|
@ -3680,6 +3774,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
if (encryptionMac != null) 'encryption_mac': encryptionMac,
|
if (encryptionMac != null) 'encryption_mac': encryptionMac,
|
||||||
if (encryptionNonce != null) 'encryption_nonce': encryptionNonce,
|
if (encryptionNonce != null) 'encryption_nonce': encryptionNonce,
|
||||||
if (storedFileHash != null) 'stored_file_hash': storedFileHash,
|
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 (createdAt != null) 'created_at': createdAt,
|
||||||
if (createdAtMonth != null) 'created_at_month': createdAtMonth,
|
if (createdAtMonth != null) 'created_at_month': createdAtMonth,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
|
|
@ -3705,6 +3801,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
Value<Uint8List?>? encryptionMac,
|
Value<Uint8List?>? encryptionMac,
|
||||||
Value<Uint8List?>? encryptionNonce,
|
Value<Uint8List?>? encryptionNonce,
|
||||||
Value<Uint8List?>? storedFileHash,
|
Value<Uint8List?>? storedFileHash,
|
||||||
|
Value<bool>? hasThumbnail,
|
||||||
|
Value<int?>? sizeInBytes,
|
||||||
Value<DateTime>? createdAt,
|
Value<DateTime>? createdAt,
|
||||||
Value<String?>? createdAtMonth,
|
Value<String?>? createdAtMonth,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
|
|
@ -3731,6 +3829,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
encryptionMac: encryptionMac ?? this.encryptionMac,
|
encryptionMac: encryptionMac ?? this.encryptionMac,
|
||||||
encryptionNonce: encryptionNonce ?? this.encryptionNonce,
|
encryptionNonce: encryptionNonce ?? this.encryptionNonce,
|
||||||
storedFileHash: storedFileHash ?? this.storedFileHash,
|
storedFileHash: storedFileHash ?? this.storedFileHash,
|
||||||
|
hasThumbnail: hasThumbnail ?? this.hasThumbnail,
|
||||||
|
sizeInBytes: sizeInBytes ?? this.sizeInBytes,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
createdAtMonth: createdAtMonth ?? this.createdAtMonth,
|
createdAtMonth: createdAtMonth ?? this.createdAtMonth,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
|
|
@ -3810,6 +3910,12 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
if (storedFileHash.present) {
|
if (storedFileHash.present) {
|
||||||
map['stored_file_hash'] = Variable<Uint8List>(storedFileHash.value);
|
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) {
|
if (createdAt.present) {
|
||||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
}
|
}
|
||||||
|
|
@ -3843,6 +3949,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
..write('encryptionMac: $encryptionMac, ')
|
..write('encryptionMac: $encryptionMac, ')
|
||||||
..write('encryptionNonce: $encryptionNonce, ')
|
..write('encryptionNonce: $encryptionNonce, ')
|
||||||
..write('storedFileHash: $storedFileHash, ')
|
..write('storedFileHash: $storedFileHash, ')
|
||||||
|
..write('hasThumbnail: $hasThumbnail, ')
|
||||||
|
..write('sizeInBytes: $sizeInBytes, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('createdAtMonth: $createdAtMonth, ')
|
..write('createdAtMonth: $createdAtMonth, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
|
|
@ -15344,6 +15452,8 @@ typedef $$MediaFilesTableCreateCompanionBuilder =
|
||||||
Value<Uint8List?> encryptionMac,
|
Value<Uint8List?> encryptionMac,
|
||||||
Value<Uint8List?> encryptionNonce,
|
Value<Uint8List?> encryptionNonce,
|
||||||
Value<Uint8List?> storedFileHash,
|
Value<Uint8List?> storedFileHash,
|
||||||
|
Value<bool> hasThumbnail,
|
||||||
|
Value<int?> sizeInBytes,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<String?> createdAtMonth,
|
Value<String?> createdAtMonth,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
|
|
@ -15368,6 +15478,8 @@ typedef $$MediaFilesTableUpdateCompanionBuilder =
|
||||||
Value<Uint8List?> encryptionMac,
|
Value<Uint8List?> encryptionMac,
|
||||||
Value<Uint8List?> encryptionNonce,
|
Value<Uint8List?> encryptionNonce,
|
||||||
Value<Uint8List?> storedFileHash,
|
Value<Uint8List?> storedFileHash,
|
||||||
|
Value<bool> hasThumbnail,
|
||||||
|
Value<int?> sizeInBytes,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<String?> createdAtMonth,
|
Value<String?> createdAtMonth,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
|
|
@ -15499,6 +15611,16 @@ class $$MediaFilesTableFilterComposer
|
||||||
builder: (column) => ColumnFilters(column),
|
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(
|
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||||
column: $table.createdAt,
|
column: $table.createdAt,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
|
|
@ -15634,6 +15756,16 @@ class $$MediaFilesTableOrderingComposer
|
||||||
builder: (column) => ColumnOrderings(column),
|
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(
|
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||||
column: $table.createdAt,
|
column: $table.createdAt,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
|
@ -15741,6 +15873,16 @@ class $$MediaFilesTableAnnotationComposer
|
||||||
builder: (column) => column,
|
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 =>
|
GeneratedColumn<DateTime> get createdAt =>
|
||||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -15821,6 +15963,8 @@ class $$MediaFilesTableTableManager
|
||||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||||
Value<Uint8List?> storedFileHash = 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<DateTime> createdAt = const Value.absent(),
|
||||||
Value<String?> createdAtMonth = const Value.absent(),
|
Value<String?> createdAtMonth = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
|
|
@ -15843,6 +15987,8 @@ class $$MediaFilesTableTableManager
|
||||||
encryptionMac: encryptionMac,
|
encryptionMac: encryptionMac,
|
||||||
encryptionNonce: encryptionNonce,
|
encryptionNonce: encryptionNonce,
|
||||||
storedFileHash: storedFileHash,
|
storedFileHash: storedFileHash,
|
||||||
|
hasThumbnail: hasThumbnail,
|
||||||
|
sizeInBytes: sizeInBytes,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
createdAtMonth: createdAtMonth,
|
createdAtMonth: createdAtMonth,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
|
|
@ -15867,6 +16013,8 @@ class $$MediaFilesTableTableManager
|
||||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||||
Value<Uint8List?> storedFileHash = 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<DateTime> createdAt = const Value.absent(),
|
||||||
Value<String?> createdAtMonth = const Value.absent(),
|
Value<String?> createdAtMonth = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
|
|
@ -15889,6 +16037,8 @@ class $$MediaFilesTableTableManager
|
||||||
encryptionMac: encryptionMac,
|
encryptionMac: encryptionMac,
|
||||||
encryptionNonce: encryptionNonce,
|
encryptionNonce: encryptionNonce,
|
||||||
storedFileHash: storedFileHash,
|
storedFileHash: storedFileHash,
|
||||||
|
hasThumbnail: hasThumbnail,
|
||||||
|
sizeInBytes: sizeInBytes,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
createdAtMonth: createdAtMonth,
|
createdAtMonth: createdAtMonth,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
|
|
|
||||||
|
|
@ -8032,6 +8032,519 @@ i1.GeneratedColumn<i2.Uint8List> _column_243(String aliasedName) =>
|
||||||
type: i1.DriftSqlType.blob,
|
type: i1.DriftSqlType.blob,
|
||||||
$customConstraints: 'NOT NULL',
|
$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({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
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, Schema13 schema) from12To13,
|
||||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
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, Schema15 schema) from14To15,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -8120,6 +8634,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from14To15(migrator, schema);
|
await from14To15(migrator, schema);
|
||||||
return 15;
|
return 15;
|
||||||
|
case 15:
|
||||||
|
final schema = Schema16(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from15To16(migrator, schema);
|
||||||
|
return 16;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
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, Schema13 schema) from12To13,
|
||||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
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, Schema15 schema) from14To15,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
|
|
@ -8157,5 +8677,6 @@ i1.OnUpgrade stepByStep({
|
||||||
from12To13: from12To13,
|
from12To13: from12To13,
|
||||||
from13To14: from13To14,
|
from13To14: from13To14,
|
||||||
from14To15: from14To15,
|
from14To15: from14To15,
|
||||||
|
from15To16: from15To16,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||||
..requestedAudioPermission =
|
..requestedAudioPermission =
|
||||||
json['requestedAudioPermission'] as bool? ?? false
|
json['requestedAudioPermission'] as bool? ?? false
|
||||||
|
..automaticallyMarkEqualMediaFilesAsOpened =
|
||||||
|
json['automaticallyMarkEqualMediaFilesAsOpened'] as bool? ?? false
|
||||||
..videoStabilizationEnabled =
|
..videoStabilizationEnabled =
|
||||||
json['videoStabilizationEnabled'] as bool? ?? true
|
json['videoStabilizationEnabled'] as bool? ?? true
|
||||||
..showFeedbackShortcut = json['showFeedbackShortcut'] 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]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
'defaultShowTime': instance.defaultShowTime,
|
'defaultShowTime': instance.defaultShowTime,
|
||||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||||
|
'automaticallyMarkEqualMediaFilesAsOpened':
|
||||||
|
instance.automaticallyMarkEqualMediaFilesAsOpened,
|
||||||
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
|
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
|
||||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||||
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
|
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
|
||||||
|
|
|
||||||
|
|
@ -200,14 +200,24 @@ class MediaFileService {
|
||||||
Log.error('Could not create Thumbnail as stored media does not exists.');
|
Log.error('Could not create Thumbnail as stored media does not exists.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var success = false;
|
||||||
switch (mediaFile.type) {
|
switch (mediaFile.type) {
|
||||||
case MediaType.gif:
|
case MediaType.gif:
|
||||||
case MediaType.audio:
|
success = await createThumbnailsForGif(storedPath, thumbnailPath);
|
||||||
case MediaType.image:
|
case MediaType.image:
|
||||||
// all images are already compress..
|
success = await createThumbnailsForImage(storedPath, thumbnailPath);
|
||||||
break;
|
|
||||||
case MediaType.video:
|
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();
|
tempPath.existsSync();
|
||||||
|
|
||||||
bool get imagePreviewAvailable =>
|
bool get imagePreviewAvailable =>
|
||||||
thumbnailPath.existsSync() || storedPath.existsSync();
|
mediaFile.hasThumbnail ||
|
||||||
|
thumbnailPath.existsSync() ||
|
||||||
|
storedPath.existsSync();
|
||||||
|
|
||||||
Future<void> storeMediaFile() async {
|
Future<void> storeMediaFile() async {
|
||||||
Log.info('Storing media file ${mediaFile.mediaId}');
|
Log.info('Storing media file ${mediaFile.mediaId}');
|
||||||
|
|
@ -284,10 +296,24 @@ class MediaFileService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
unawaited(createThumbnail());
|
unawaited(createThumbnail());
|
||||||
|
await calculateAndSaveSize();
|
||||||
await hashMediaFile();
|
await hashMediaFile();
|
||||||
// updateFromDb is done in hashStoredMedia()
|
// 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 {
|
Future<void> hashMediaFile() async {
|
||||||
late final List<int> checksum;
|
late final List<int> checksum;
|
||||||
if (storedPath.existsSync()) {
|
if (storedPath.existsSync()) {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
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:pro_video_editor/pro_video_editor.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
Future<void> createThumbnailsForVideo(
|
Future<bool> createThumbnailsForVideo(
|
||||||
File sourceFile,
|
File sourceFile,
|
||||||
File destinationFile,
|
File destinationFile,
|
||||||
) async {
|
) async {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
if (destinationFile.existsSync()) {
|
if (destinationFile.existsSync()) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final images = await ProVideoEditor.instance.getThumbnails(
|
final images = await ProVideoEditor.instance.getThumbnails(
|
||||||
|
|
@ -28,11 +30,89 @@ Future<void> createThumbnailsForVideo(
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
destinationFile.writeAsBytesSync(images.first);
|
destinationFile.writeAsBytesSync(images.first);
|
||||||
Log.info(
|
Log.info(
|
||||||
'It took ${stopwatch.elapsedMilliseconds}ms to create the thumbnail.',
|
'It took ${stopwatch.elapsedMilliseconds}ms to create the video thumbnail.',
|
||||||
);
|
);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Log.warn(
|
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 'dart:collection';
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
|
|
@ -170,25 +171,38 @@ class MemoriesService {
|
||||||
|
|
||||||
Future<void> _initAsync() async {
|
Future<void> _initAsync() async {
|
||||||
try {
|
try {
|
||||||
// 1. Perform Inventory / Migration of non-hashed stored files
|
// 1. Perform Inventory / Migration of stored files
|
||||||
final nonHashedFiles = await twonlyDB.mediaFilesDao
|
final pendingFiles = await twonlyDB.mediaFilesDao
|
||||||
.getAllNonHashedStoredMediaFiles();
|
.getAllMediaFilesPendingMigration();
|
||||||
final unanalyzedFiles = await twonlyDB.mediaFilesDao
|
|
||||||
.getAllUnanalyzedStoredMediaFiles();
|
|
||||||
|
|
||||||
final totalToMigrate = nonHashedFiles.length + unanalyzedFiles.length;
|
if (pendingFiles.isNotEmpty) {
|
||||||
if (totalToMigrate > 0) {
|
_updateState(filesToMigrate: pendingFiles.length);
|
||||||
_updateState(filesToMigrate: totalToMigrate);
|
|
||||||
|
|
||||||
for (final mediaFile in nonHashedFiles) {
|
for (final mediaFile in pendingFiles) {
|
||||||
final mediaService = MediaFileService(mediaFile);
|
final mediaService = MediaFileService(mediaFile);
|
||||||
await mediaService.hashMediaFile();
|
|
||||||
_updateState(filesToMigrate: _currentState.filesToMigrate - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final mediaFile in unanalyzedFiles) {
|
if (mediaService.mediaFile.storedFileHash == null) {
|
||||||
final mediaService = MediaFileService(mediaFile);
|
await mediaService.hashMediaFile();
|
||||||
await mediaService.cropTransparentBorders();
|
}
|
||||||
|
|
||||||
|
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);
|
_updateState(filesToMigrate: _currentState.filesToMigrate - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,10 +248,9 @@ class MemoriesService {
|
||||||
final mediaService = MediaFileService(mediaFile);
|
final mediaService = MediaFileService(mediaFile);
|
||||||
if (!mediaService.imagePreviewAvailable) continue;
|
if (!mediaService.imagePreviewAvailable) continue;
|
||||||
|
|
||||||
if (mediaService.mediaFile.type == MediaType.video) {
|
if (!mediaService.mediaFile.hasThumbnail &&
|
||||||
if (!mediaService.thumbnailPath.existsSync()) {
|
mediaService.mediaFile.type != MediaType.audio) {
|
||||||
unawaited(mediaService.createThumbnail());
|
unawaited(mediaService.createThumbnail());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final senderContact = mediaIdToSenderContact[mediaFile.mediaId];
|
final senderContact = mediaIdToSenderContact[mediaFile.mediaId];
|
||||||
|
|
|
||||||
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(
|
bottomNavigationBar: AnimatedSize(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
child: _isBottomNavVisible
|
child: (_activePageIdx != 2 || _isBottomNavVisible)
|
||||||
? BottomNavigationBar(
|
? BottomNavigationBar(
|
||||||
showSelectedLabels: false,
|
showSelectedLabels: false,
|
||||||
showUnselectedLabels: false,
|
showUnselectedLabels: false,
|
||||||
unselectedIconTheme: IconThemeData(
|
unselectedIconTheme: IconThemeData(
|
||||||
color: Theme.of(context)
|
color: Theme.of(
|
||||||
.colorScheme
|
context,
|
||||||
.inverseSurface
|
).colorScheme.inverseSurface.withAlpha(150),
|
||||||
.withAlpha(150),
|
|
||||||
),
|
),
|
||||||
selectedIconTheme: IconThemeData(
|
selectedIconTheme: IconThemeData(
|
||||||
color: Theme.of(context).colorScheme.inverseSurface,
|
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/services/memories/memories.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/alert.dialog.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/components/snackbar.dart';
|
||||||
import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart';
|
import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart';
|
||||||
import 'package:twonly/src/visual/views/memories/components/flashback_banner.comp.dart';
|
import 'package:twonly/src/visual/views/memories/components/flashback_banner.comp.dart';
|
||||||
|
|
@ -371,99 +372,128 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
orderedByMonth = filteredOrdered;
|
orderedByMonth = filteredOrdered;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scrollbar(
|
return LayoutBuilder(
|
||||||
controller: _scrollController,
|
builder: (context, constraints) {
|
||||||
thickness: 12,
|
return DraggableScrollbar(
|
||||||
radius: const Radius.circular(6),
|
controller: _scrollController,
|
||||||
interactive: true,
|
labelBuilder: (offset) {
|
||||||
child: CustomScrollView(
|
final state = _service.currentState;
|
||||||
controller: _scrollController,
|
if (state.isEmpty) return null;
|
||||||
physics: const BouncingScrollPhysics(),
|
|
||||||
slivers: [
|
// Simple heuristic to find month by offset
|
||||||
SliverAppBar(
|
double currentOffset = 56;
|
||||||
title: const Text(
|
if (state.galleryItemsLastYears.isNotEmpty) {
|
||||||
'Memories',
|
currentOffset += 220;
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
}
|
||||||
),
|
|
||||||
floating: true,
|
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||||
snap: true,
|
final itemWidth = (screenWidth - 8) / 4;
|
||||||
elevation: 0,
|
final itemHeight = itemWidth * (16 / 9);
|
||||||
backgroundColor: context.color.surface,
|
final rowHeight = itemHeight + 2;
|
||||||
actions: [
|
|
||||||
IconButton(
|
for (final month in state.months) {
|
||||||
icon: Icon(
|
final indices = state.orderedByMonth[month]!;
|
||||||
_filterFavoritesOnly
|
final totalRows = (indices.length + 3) ~/ 4;
|
||||||
? Icons.favorite
|
final monthHeight = 44 + (totalRows * rowHeight);
|
||||||
: Icons.favorite_border,
|
|
||||||
color: _filterFavoritesOnly
|
if (offset < currentOffset + monthHeight) {
|
||||||
? Colors.redAccent
|
return month;
|
||||||
: null,
|
}
|
||||||
|
currentOffset += monthHeight;
|
||||||
|
}
|
||||||
|
return state.months.last;
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
title: const Text(
|
||||||
|
'Memories',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
floating: true,
|
||||||
setState(() {
|
snap: true,
|
||||||
_filterFavoritesOnly = !_filterFavoritesOnly;
|
elevation: 0,
|
||||||
});
|
backgroundColor: context.color.surface,
|
||||||
},
|
actions: [
|
||||||
tooltip: _filterFavoritesOnly
|
IconButton(
|
||||||
? 'Show all'
|
icon: Icon(
|
||||||
: 'Show favorites only',
|
_filterFavoritesOnly
|
||||||
|
? Icons.favorite
|
||||||
|
: Icons.favorite_border,
|
||||||
|
color: _filterFavoritesOnly
|
||||||
|
? Colors.redAccent
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_filterFavoritesOnly = !_filterFavoritesOnly;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tooltip: _filterFavoritesOnly
|
||||||
|
? 'Show all'
|
||||||
|
: 'Show favorites only',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: Text(
|
||||||
|
month,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverGrid(
|
||||||
|
gridDelegate:
|
||||||
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 4,
|
||||||
|
mainAxisSpacing: 2,
|
||||||
|
crossAxisSpacing: 2,
|
||||||
|
childAspectRatio: 9 / 16,
|
||||||
|
),
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, idx) {
|
||||||
|
final globalIndex = orderedByMonth[month]![idx];
|
||||||
|
final item = state.galleryItems[globalIndex];
|
||||||
|
final mediaId =
|
||||||
|
item.mediaService.mediaFile.mediaId;
|
||||||
|
final isSelected = _selectedMediaIds.contains(
|
||||||
|
mediaId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return MemoriesThumbnailComp(
|
||||||
|
galleryItem: item,
|
||||||
|
index: globalIndex,
|
||||||
|
selectionMode: _selectionMode,
|
||||||
|
isSelected: isSelected,
|
||||||
|
activeMediaIdNotifier: _activeMediaIdNotifier,
|
||||||
|
onLongPress: () => _onLongPressItem(mediaId),
|
||||||
|
onTap: () => _onTapItem(mediaId, globalIndex),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount: orderedByMonth[month]!.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SliverPadding(
|
||||||
|
padding: EdgeInsets.only(bottom: 32),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
);
|
||||||
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),
|
|
||||||
sliver: SliverToBoxAdapter(
|
|
||||||
child: Text(
|
|
||||||
month,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverGrid(
|
|
||||||
gridDelegate:
|
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 4,
|
|
||||||
mainAxisSpacing: 2,
|
|
||||||
crossAxisSpacing: 2,
|
|
||||||
childAspectRatio: 9 / 16,
|
|
||||||
),
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, idx) {
|
|
||||||
final globalIndex = orderedByMonth[month]![idx];
|
|
||||||
final item = state.galleryItems[globalIndex];
|
|
||||||
final mediaId = item.mediaService.mediaFile.mediaId;
|
|
||||||
final isSelected = _selectedMediaIds.contains(
|
|
||||||
mediaId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return MemoriesThumbnailComp(
|
|
||||||
galleryItem: item,
|
|
||||||
index: globalIndex,
|
|
||||||
selectionMode: _selectionMode,
|
|
||||||
isSelected: isSelected,
|
|
||||||
activeMediaIdNotifier: _activeMediaIdNotifier,
|
|
||||||
onLongPress: () => _onLongPressItem(mediaId),
|
|
||||||
onTap: () => _onTapItem(mediaId, globalIndex),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
childCount: orderedByMonth[month]!.length,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SliverPadding(padding: EdgeInsets.only(bottom: 32)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
|
||||||
isDraftMedia: false,
|
isDraftMedia: false,
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
hasCropAnalyzed: false,
|
hasCropAnalyzed: false,
|
||||||
|
hasThumbnail: false,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
);
|
);
|
||||||
final mediaService = MediaFileService(mediaFile);
|
final mediaService = MediaFileService(mediaFile);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import 'schema_v12.dart' as v12;
|
||||||
import 'schema_v13.dart' as v13;
|
import 'schema_v13.dart' as v13;
|
||||||
import 'schema_v14.dart' as v14;
|
import 'schema_v14.dart' as v14;
|
||||||
import 'schema_v15.dart' as v15;
|
import 'schema_v15.dart' as v15;
|
||||||
|
import 'schema_v16.dart' as v16;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
|
|
@ -54,6 +55,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
return v14.DatabaseAtV14(db);
|
return v14.DatabaseAtV14(db);
|
||||||
case 15:
|
case 15:
|
||||||
return v15.DatabaseAtV15(db);
|
return v15.DatabaseAtV15(db);
|
||||||
|
case 16:
|
||||||
|
return v16.DatabaseAtV16(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
|
|
@ -75,5 +78,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
13,
|
13,
|
||||||
14,
|
14,
|
||||||
15,
|
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