This commit is contained in:
otsmr 2025-04-29 16:42:05 +02:00
parent 0d36289169
commit 3b65e7d7d1
15 changed files with 3351 additions and 55 deletions

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,8 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api/media_received.dart';
import 'package:twonly/src/providers/api/media_send.dart';
import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/api_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/providers/connection_provider.dart'; import 'package:twonly/src/providers/connection_provider.dart';
@ -32,6 +34,8 @@ void main() async {
apiProvider = ApiProvider(); apiProvider = ApiProvider();
twonlyDatabase = TwonlyDatabase(); twonlyDatabase = TwonlyDatabase();
await twonlyDatabase.messagesDao.resetPendingDownloadState(); await twonlyDatabase.messagesDao.resetPendingDownloadState();
await purgeReceivedMediaFiles();
await purgeSendMediaFiles();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);

View file

@ -49,6 +49,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
t.contactId.equals(contactId) & t.contactId.equals(contactId) &
t.contentJson.isNotNull() & t.contentJson.isNotNull() &
(t.openedAt.isNull() | (t.openedAt.isNull() |
t.mediaStored.equals(true) |
t.openedAt.isBiggerThanValue( t.openedAt.isBiggerThanValue(
DateTime.now().subtract(Duration(days: 1))))) DateTime.now().subtract(Duration(days: 1)))))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)])) ..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))

View file

@ -34,6 +34,8 @@ class Messages extends Table {
IntColumn get responseToOtherMessageId => integer().nullable()(); IntColumn get responseToOtherMessageId => integer().nullable()();
BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))(); BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))();
BoolColumn get mediaStored => boolean().withDefault(Constant(false))();
IntColumn get downloadState => intEnum<DownloadState>() IntColumn get downloadState => intEnum<DownloadState>()
.withDefault(Constant(DownloadState.downloaded.index))(); .withDefault(Constant(DownloadState.downloaded.index))();

View file

@ -43,7 +43,7 @@ class TwonlyDatabase extends _$TwonlyDatabase {
TwonlyDatabase.forTesting(DatabaseConnection super.connection); TwonlyDatabase.forTesting(DatabaseConnection super.connection);
@override @override
int get schemaVersion => 5; int get schemaVersion => 6;
static QueryExecutor _openConnection() { static QueryExecutor _openConnection() {
return driftDatabase( return driftDatabase(
@ -69,6 +69,8 @@ class TwonlyDatabase extends _$TwonlyDatabase {
m.createTable(mediaDownloads); m.createTable(mediaDownloads);
m.addColumn(schema.messages, schema.messages.mediaDownloadId); m.addColumn(schema.messages, schema.messages.mediaDownloadId);
m.addColumn(schema.messages, schema.messages.mediaUploadId); m.addColumn(schema.messages, schema.messages.mediaUploadId);
}, from5To6: (m, schema) async {
m.addColumn(schema.messages, schema.messages.mediaStored);
}), }),
); );
} }

View file

@ -992,6 +992,16 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
defaultConstraints: GeneratedColumn.constraintIsAlways( defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("acknowledge_by_user" IN (0, 1))'), 'CHECK ("acknowledge_by_user" IN (0, 1))'),
defaultValue: Constant(false)); defaultValue: Constant(false));
static const VerificationMeta _mediaStoredMeta =
const VerificationMeta('mediaStored');
@override
late final GeneratedColumn<bool> mediaStored = GeneratedColumn<bool>(
'media_stored', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("media_stored" IN (0, 1))'),
defaultValue: Constant(false));
@override @override
late final GeneratedColumnWithTypeConverter<DownloadState, int> late final GeneratedColumnWithTypeConverter<DownloadState, int>
downloadState = GeneratedColumn<int>('download_state', aliasedName, false, downloadState = GeneratedColumn<int>('download_state', aliasedName, false,
@ -1061,6 +1071,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
responseToMessageId, responseToMessageId,
responseToOtherMessageId, responseToOtherMessageId,
acknowledgeByUser, acknowledgeByUser,
mediaStored,
downloadState, downloadState,
acknowledgeByServer, acknowledgeByServer,
errorWhileSending, errorWhileSending,
@ -1127,6 +1138,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
acknowledgeByUser.isAcceptableOrUnknown( acknowledgeByUser.isAcceptableOrUnknown(
data['acknowledge_by_user']!, _acknowledgeByUserMeta)); data['acknowledge_by_user']!, _acknowledgeByUserMeta));
} }
if (data.containsKey('media_stored')) {
context.handle(
_mediaStoredMeta,
mediaStored.isAcceptableOrUnknown(
data['media_stored']!, _mediaStoredMeta));
}
if (data.containsKey('acknowledge_by_server')) { if (data.containsKey('acknowledge_by_server')) {
context.handle( context.handle(
_acknowledgeByServerMeta, _acknowledgeByServerMeta,
@ -1183,6 +1200,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
data['${effectivePrefix}response_to_other_message_id']), data['${effectivePrefix}response_to_other_message_id']),
acknowledgeByUser: attachedDatabase.typeMapping.read( acknowledgeByUser: attachedDatabase.typeMapping.read(
DriftSqlType.bool, data['${effectivePrefix}acknowledge_by_user'])!, DriftSqlType.bool, data['${effectivePrefix}acknowledge_by_user'])!,
mediaStored: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}media_stored'])!,
downloadState: $MessagesTable.$converterdownloadState.fromSql( downloadState: $MessagesTable.$converterdownloadState.fromSql(
attachedDatabase.typeMapping.read( attachedDatabase.typeMapping.read(
DriftSqlType.int, data['${effectivePrefix}download_state'])!), DriftSqlType.int, data['${effectivePrefix}download_state'])!),
@ -1223,6 +1242,7 @@ class Message extends DataClass implements Insertable<Message> {
final int? responseToMessageId; final int? responseToMessageId;
final int? responseToOtherMessageId; final int? responseToOtherMessageId;
final bool acknowledgeByUser; final bool acknowledgeByUser;
final bool mediaStored;
final DownloadState downloadState; final DownloadState downloadState;
final bool acknowledgeByServer; final bool acknowledgeByServer;
final bool errorWhileSending; final bool errorWhileSending;
@ -1240,6 +1260,7 @@ class Message extends DataClass implements Insertable<Message> {
this.responseToMessageId, this.responseToMessageId,
this.responseToOtherMessageId, this.responseToOtherMessageId,
required this.acknowledgeByUser, required this.acknowledgeByUser,
required this.mediaStored,
required this.downloadState, required this.downloadState,
required this.acknowledgeByServer, required this.acknowledgeByServer,
required this.errorWhileSending, required this.errorWhileSending,
@ -1270,6 +1291,7 @@ class Message extends DataClass implements Insertable<Message> {
Variable<int>(responseToOtherMessageId); Variable<int>(responseToOtherMessageId);
} }
map['acknowledge_by_user'] = Variable<bool>(acknowledgeByUser); map['acknowledge_by_user'] = Variable<bool>(acknowledgeByUser);
map['media_stored'] = Variable<bool>(mediaStored);
{ {
map['download_state'] = Variable<int>( map['download_state'] = Variable<int>(
$MessagesTable.$converterdownloadState.toSql(downloadState)); $MessagesTable.$converterdownloadState.toSql(downloadState));
@ -1310,6 +1332,7 @@ class Message extends DataClass implements Insertable<Message> {
? const Value.absent() ? const Value.absent()
: Value(responseToOtherMessageId), : Value(responseToOtherMessageId),
acknowledgeByUser: Value(acknowledgeByUser), acknowledgeByUser: Value(acknowledgeByUser),
mediaStored: Value(mediaStored),
downloadState: Value(downloadState), downloadState: Value(downloadState),
acknowledgeByServer: Value(acknowledgeByServer), acknowledgeByServer: Value(acknowledgeByServer),
errorWhileSending: Value(errorWhileSending), errorWhileSending: Value(errorWhileSending),
@ -1339,6 +1362,7 @@ class Message extends DataClass implements Insertable<Message> {
responseToOtherMessageId: responseToOtherMessageId:
serializer.fromJson<int?>(json['responseToOtherMessageId']), serializer.fromJson<int?>(json['responseToOtherMessageId']),
acknowledgeByUser: serializer.fromJson<bool>(json['acknowledgeByUser']), acknowledgeByUser: serializer.fromJson<bool>(json['acknowledgeByUser']),
mediaStored: serializer.fromJson<bool>(json['mediaStored']),
downloadState: $MessagesTable.$converterdownloadState downloadState: $MessagesTable.$converterdownloadState
.fromJson(serializer.fromJson<int>(json['downloadState'])), .fromJson(serializer.fromJson<int>(json['downloadState'])),
acknowledgeByServer: acknowledgeByServer:
@ -1365,6 +1389,7 @@ class Message extends DataClass implements Insertable<Message> {
'responseToOtherMessageId': 'responseToOtherMessageId':
serializer.toJson<int?>(responseToOtherMessageId), serializer.toJson<int?>(responseToOtherMessageId),
'acknowledgeByUser': serializer.toJson<bool>(acknowledgeByUser), 'acknowledgeByUser': serializer.toJson<bool>(acknowledgeByUser),
'mediaStored': serializer.toJson<bool>(mediaStored),
'downloadState': serializer.toJson<int>( 'downloadState': serializer.toJson<int>(
$MessagesTable.$converterdownloadState.toJson(downloadState)), $MessagesTable.$converterdownloadState.toJson(downloadState)),
'acknowledgeByServer': serializer.toJson<bool>(acknowledgeByServer), 'acknowledgeByServer': serializer.toJson<bool>(acknowledgeByServer),
@ -1387,6 +1412,7 @@ class Message extends DataClass implements Insertable<Message> {
Value<int?> responseToMessageId = const Value.absent(), Value<int?> responseToMessageId = const Value.absent(),
Value<int?> responseToOtherMessageId = const Value.absent(), Value<int?> responseToOtherMessageId = const Value.absent(),
bool? acknowledgeByUser, bool? acknowledgeByUser,
bool? mediaStored,
DownloadState? downloadState, DownloadState? downloadState,
bool? acknowledgeByServer, bool? acknowledgeByServer,
bool? errorWhileSending, bool? errorWhileSending,
@ -1412,6 +1438,7 @@ class Message extends DataClass implements Insertable<Message> {
? responseToOtherMessageId.value ? responseToOtherMessageId.value
: this.responseToOtherMessageId, : this.responseToOtherMessageId,
acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser, acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser,
mediaStored: mediaStored ?? this.mediaStored,
downloadState: downloadState ?? this.downloadState, downloadState: downloadState ?? this.downloadState,
acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer, acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer,
errorWhileSending: errorWhileSending ?? this.errorWhileSending, errorWhileSending: errorWhileSending ?? this.errorWhileSending,
@ -1443,6 +1470,8 @@ class Message extends DataClass implements Insertable<Message> {
acknowledgeByUser: data.acknowledgeByUser.present acknowledgeByUser: data.acknowledgeByUser.present
? data.acknowledgeByUser.value ? data.acknowledgeByUser.value
: this.acknowledgeByUser, : this.acknowledgeByUser,
mediaStored:
data.mediaStored.present ? data.mediaStored.value : this.mediaStored,
downloadState: data.downloadState.present downloadState: data.downloadState.present
? data.downloadState.value ? data.downloadState.value
: this.downloadState, : this.downloadState,
@ -1472,6 +1501,7 @@ class Message extends DataClass implements Insertable<Message> {
..write('responseToMessageId: $responseToMessageId, ') ..write('responseToMessageId: $responseToMessageId, ')
..write('responseToOtherMessageId: $responseToOtherMessageId, ') ..write('responseToOtherMessageId: $responseToOtherMessageId, ')
..write('acknowledgeByUser: $acknowledgeByUser, ') ..write('acknowledgeByUser: $acknowledgeByUser, ')
..write('mediaStored: $mediaStored, ')
..write('downloadState: $downloadState, ') ..write('downloadState: $downloadState, ')
..write('acknowledgeByServer: $acknowledgeByServer, ') ..write('acknowledgeByServer: $acknowledgeByServer, ')
..write('errorWhileSending: $errorWhileSending, ') ..write('errorWhileSending: $errorWhileSending, ')
@ -1494,6 +1524,7 @@ class Message extends DataClass implements Insertable<Message> {
responseToMessageId, responseToMessageId,
responseToOtherMessageId, responseToOtherMessageId,
acknowledgeByUser, acknowledgeByUser,
mediaStored,
downloadState, downloadState,
acknowledgeByServer, acknowledgeByServer,
errorWhileSending, errorWhileSending,
@ -1514,6 +1545,7 @@ class Message extends DataClass implements Insertable<Message> {
other.responseToMessageId == this.responseToMessageId && other.responseToMessageId == this.responseToMessageId &&
other.responseToOtherMessageId == this.responseToOtherMessageId && other.responseToOtherMessageId == this.responseToOtherMessageId &&
other.acknowledgeByUser == this.acknowledgeByUser && other.acknowledgeByUser == this.acknowledgeByUser &&
other.mediaStored == this.mediaStored &&
other.downloadState == this.downloadState && other.downloadState == this.downloadState &&
other.acknowledgeByServer == this.acknowledgeByServer && other.acknowledgeByServer == this.acknowledgeByServer &&
other.errorWhileSending == this.errorWhileSending && other.errorWhileSending == this.errorWhileSending &&
@ -1533,6 +1565,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
final Value<int?> responseToMessageId; final Value<int?> responseToMessageId;
final Value<int?> responseToOtherMessageId; final Value<int?> responseToOtherMessageId;
final Value<bool> acknowledgeByUser; final Value<bool> acknowledgeByUser;
final Value<bool> mediaStored;
final Value<DownloadState> downloadState; final Value<DownloadState> downloadState;
final Value<bool> acknowledgeByServer; final Value<bool> acknowledgeByServer;
final Value<bool> errorWhileSending; final Value<bool> errorWhileSending;
@ -1550,6 +1583,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
this.responseToMessageId = const Value.absent(), this.responseToMessageId = const Value.absent(),
this.responseToOtherMessageId = const Value.absent(), this.responseToOtherMessageId = const Value.absent(),
this.acknowledgeByUser = const Value.absent(), this.acknowledgeByUser = const Value.absent(),
this.mediaStored = const Value.absent(),
this.downloadState = const Value.absent(), this.downloadState = const Value.absent(),
this.acknowledgeByServer = const Value.absent(), this.acknowledgeByServer = const Value.absent(),
this.errorWhileSending = const Value.absent(), this.errorWhileSending = const Value.absent(),
@ -1568,6 +1602,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
this.responseToMessageId = const Value.absent(), this.responseToMessageId = const Value.absent(),
this.responseToOtherMessageId = const Value.absent(), this.responseToOtherMessageId = const Value.absent(),
this.acknowledgeByUser = const Value.absent(), this.acknowledgeByUser = const Value.absent(),
this.mediaStored = const Value.absent(),
this.downloadState = const Value.absent(), this.downloadState = const Value.absent(),
this.acknowledgeByServer = const Value.absent(), this.acknowledgeByServer = const Value.absent(),
this.errorWhileSending = const Value.absent(), this.errorWhileSending = const Value.absent(),
@ -1587,6 +1622,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
Expression<int>? responseToMessageId, Expression<int>? responseToMessageId,
Expression<int>? responseToOtherMessageId, Expression<int>? responseToOtherMessageId,
Expression<bool>? acknowledgeByUser, Expression<bool>? acknowledgeByUser,
Expression<bool>? mediaStored,
Expression<int>? downloadState, Expression<int>? downloadState,
Expression<bool>? acknowledgeByServer, Expression<bool>? acknowledgeByServer,
Expression<bool>? errorWhileSending, Expression<bool>? errorWhileSending,
@ -1607,6 +1643,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
if (responseToOtherMessageId != null) if (responseToOtherMessageId != null)
'response_to_other_message_id': responseToOtherMessageId, 'response_to_other_message_id': responseToOtherMessageId,
if (acknowledgeByUser != null) 'acknowledge_by_user': acknowledgeByUser, if (acknowledgeByUser != null) 'acknowledge_by_user': acknowledgeByUser,
if (mediaStored != null) 'media_stored': mediaStored,
if (downloadState != null) 'download_state': downloadState, if (downloadState != null) 'download_state': downloadState,
if (acknowledgeByServer != null) if (acknowledgeByServer != null)
'acknowledge_by_server': acknowledgeByServer, 'acknowledge_by_server': acknowledgeByServer,
@ -1628,6 +1665,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
Value<int?>? responseToMessageId, Value<int?>? responseToMessageId,
Value<int?>? responseToOtherMessageId, Value<int?>? responseToOtherMessageId,
Value<bool>? acknowledgeByUser, Value<bool>? acknowledgeByUser,
Value<bool>? mediaStored,
Value<DownloadState>? downloadState, Value<DownloadState>? downloadState,
Value<bool>? acknowledgeByServer, Value<bool>? acknowledgeByServer,
Value<bool>? errorWhileSending, Value<bool>? errorWhileSending,
@ -1646,6 +1684,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
responseToOtherMessageId: responseToOtherMessageId:
responseToOtherMessageId ?? this.responseToOtherMessageId, responseToOtherMessageId ?? this.responseToOtherMessageId,
acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser, acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser,
mediaStored: mediaStored ?? this.mediaStored,
downloadState: downloadState ?? this.downloadState, downloadState: downloadState ?? this.downloadState,
acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer, acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer,
errorWhileSending: errorWhileSending ?? this.errorWhileSending, errorWhileSending: errorWhileSending ?? this.errorWhileSending,
@ -1685,6 +1724,9 @@ class MessagesCompanion extends UpdateCompanion<Message> {
if (acknowledgeByUser.present) { if (acknowledgeByUser.present) {
map['acknowledge_by_user'] = Variable<bool>(acknowledgeByUser.value); map['acknowledge_by_user'] = Variable<bool>(acknowledgeByUser.value);
} }
if (mediaStored.present) {
map['media_stored'] = Variable<bool>(mediaStored.value);
}
if (downloadState.present) { if (downloadState.present) {
map['download_state'] = Variable<int>( map['download_state'] = Variable<int>(
$MessagesTable.$converterdownloadState.toSql(downloadState.value)); $MessagesTable.$converterdownloadState.toSql(downloadState.value));
@ -1725,6 +1767,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
..write('responseToMessageId: $responseToMessageId, ') ..write('responseToMessageId: $responseToMessageId, ')
..write('responseToOtherMessageId: $responseToOtherMessageId, ') ..write('responseToOtherMessageId: $responseToOtherMessageId, ')
..write('acknowledgeByUser: $acknowledgeByUser, ') ..write('acknowledgeByUser: $acknowledgeByUser, ')
..write('mediaStored: $mediaStored, ')
..write('downloadState: $downloadState, ') ..write('downloadState: $downloadState, ')
..write('acknowledgeByServer: $acknowledgeByServer, ') ..write('acknowledgeByServer: $acknowledgeByServer, ')
..write('errorWhileSending: $errorWhileSending, ') ..write('errorWhileSending: $errorWhileSending, ')
@ -3868,6 +3911,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
Value<int?> responseToMessageId, Value<int?> responseToMessageId,
Value<int?> responseToOtherMessageId, Value<int?> responseToOtherMessageId,
Value<bool> acknowledgeByUser, Value<bool> acknowledgeByUser,
Value<bool> mediaStored,
Value<DownloadState> downloadState, Value<DownloadState> downloadState,
Value<bool> acknowledgeByServer, Value<bool> acknowledgeByServer,
Value<bool> errorWhileSending, Value<bool> errorWhileSending,
@ -3886,6 +3930,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
Value<int?> responseToMessageId, Value<int?> responseToMessageId,
Value<int?> responseToOtherMessageId, Value<int?> responseToOtherMessageId,
Value<bool> acknowledgeByUser, Value<bool> acknowledgeByUser,
Value<bool> mediaStored,
Value<DownloadState> downloadState, Value<DownloadState> downloadState,
Value<bool> acknowledgeByServer, Value<bool> acknowledgeByServer,
Value<bool> errorWhileSending, Value<bool> errorWhileSending,
@ -3951,6 +3996,9 @@ class $$MessagesTableFilterComposer
column: $table.acknowledgeByUser, column: $table.acknowledgeByUser,
builder: (column) => ColumnFilters(column)); builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get mediaStored => $composableBuilder(
column: $table.mediaStored, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<DownloadState, DownloadState, int> ColumnWithTypeConverterFilters<DownloadState, DownloadState, int>
get downloadState => $composableBuilder( get downloadState => $composableBuilder(
column: $table.downloadState, column: $table.downloadState,
@ -4038,6 +4086,9 @@ class $$MessagesTableOrderingComposer
column: $table.acknowledgeByUser, column: $table.acknowledgeByUser,
builder: (column) => ColumnOrderings(column)); builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get mediaStored => $composableBuilder(
column: $table.mediaStored, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get downloadState => $composableBuilder( ColumnOrderings<int> get downloadState => $composableBuilder(
column: $table.downloadState, column: $table.downloadState,
builder: (column) => ColumnOrderings(column)); builder: (column) => ColumnOrderings(column));
@ -4116,6 +4167,9 @@ class $$MessagesTableAnnotationComposer
GeneratedColumn<bool> get acknowledgeByUser => $composableBuilder( GeneratedColumn<bool> get acknowledgeByUser => $composableBuilder(
column: $table.acknowledgeByUser, builder: (column) => column); column: $table.acknowledgeByUser, builder: (column) => column);
GeneratedColumn<bool> get mediaStored => $composableBuilder(
column: $table.mediaStored, builder: (column) => column);
GeneratedColumnWithTypeConverter<DownloadState, int> get downloadState => GeneratedColumnWithTypeConverter<DownloadState, int> get downloadState =>
$composableBuilder( $composableBuilder(
column: $table.downloadState, builder: (column) => column); column: $table.downloadState, builder: (column) => column);
@ -4193,6 +4247,7 @@ class $$MessagesTableTableManager extends RootTableManager<
Value<int?> responseToMessageId = const Value.absent(), Value<int?> responseToMessageId = const Value.absent(),
Value<int?> responseToOtherMessageId = const Value.absent(), Value<int?> responseToOtherMessageId = const Value.absent(),
Value<bool> acknowledgeByUser = const Value.absent(), Value<bool> acknowledgeByUser = const Value.absent(),
Value<bool> mediaStored = const Value.absent(),
Value<DownloadState> downloadState = const Value.absent(), Value<DownloadState> downloadState = const Value.absent(),
Value<bool> acknowledgeByServer = const Value.absent(), Value<bool> acknowledgeByServer = const Value.absent(),
Value<bool> errorWhileSending = const Value.absent(), Value<bool> errorWhileSending = const Value.absent(),
@ -4211,6 +4266,7 @@ class $$MessagesTableTableManager extends RootTableManager<
responseToMessageId: responseToMessageId, responseToMessageId: responseToMessageId,
responseToOtherMessageId: responseToOtherMessageId, responseToOtherMessageId: responseToOtherMessageId,
acknowledgeByUser: acknowledgeByUser, acknowledgeByUser: acknowledgeByUser,
mediaStored: mediaStored,
downloadState: downloadState, downloadState: downloadState,
acknowledgeByServer: acknowledgeByServer, acknowledgeByServer: acknowledgeByServer,
errorWhileSending: errorWhileSending, errorWhileSending: errorWhileSending,
@ -4229,6 +4285,7 @@ class $$MessagesTableTableManager extends RootTableManager<
Value<int?> responseToMessageId = const Value.absent(), Value<int?> responseToMessageId = const Value.absent(),
Value<int?> responseToOtherMessageId = const Value.absent(), Value<int?> responseToOtherMessageId = const Value.absent(),
Value<bool> acknowledgeByUser = const Value.absent(), Value<bool> acknowledgeByUser = const Value.absent(),
Value<bool> mediaStored = const Value.absent(),
Value<DownloadState> downloadState = const Value.absent(), Value<DownloadState> downloadState = const Value.absent(),
Value<bool> acknowledgeByServer = const Value.absent(), Value<bool> acknowledgeByServer = const Value.absent(),
Value<bool> errorWhileSending = const Value.absent(), Value<bool> errorWhileSending = const Value.absent(),
@ -4247,6 +4304,7 @@ class $$MessagesTableTableManager extends RootTableManager<
responseToMessageId: responseToMessageId, responseToMessageId: responseToMessageId,
responseToOtherMessageId: responseToOtherMessageId, responseToOtherMessageId: responseToOtherMessageId,
acknowledgeByUser: acknowledgeByUser, acknowledgeByUser: acknowledgeByUser,
mediaStored: mediaStored,
downloadState: downloadState, downloadState: downloadState,
acknowledgeByServer: acknowledgeByServer, acknowledgeByServer: acknowledgeByServer,
errorWhileSending: errorWhileSending, errorWhileSending: errorWhileSending,

View file

@ -1026,11 +1026,228 @@ i1.GeneratedColumn<int> _column_50(String aliasedName) =>
i1.GeneratedColumn<String> _column_51(String aliasedName) => i1.GeneratedColumn<String> _column_51(String aliasedName) =>
i1.GeneratedColumn<String>('download_token', aliasedName, false, i1.GeneratedColumn<String>('download_token', aliasedName, false,
type: i1.DriftSqlType.string); type: i1.DriftSqlType.string);
final class Schema6 extends i0.VersionedSchema {
Schema6({required super.database}) : super(version: 6);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
messages,
mediaUploads,
mediaDownloads,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
];
late final Shape6 contacts = Shape6(
source: i0.VersionedTable(
entityName: 'contacts',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(user_id)',
],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
_column_6,
_column_7,
_column_8,
_column_9,
_column_39,
_column_40,
_column_10,
_column_11,
_column_12,
_column_13,
_column_14,
_column_15,
_column_16,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 messages = Shape10(
source: i0.VersionedTable(
entityName: 'messages',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_17,
_column_18,
_column_19,
_column_48,
_column_49,
_column_20,
_column_21,
_column_22,
_column_52,
_column_23,
_column_24,
_column_25,
_column_26,
_column_27,
_column_28,
_column_29,
_column_30,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 mediaUploads = Shape7(
source: i0.VersionedTable(
entityName: 'media_uploads',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_41,
_column_42,
_column_43,
_column_44,
_column_45,
_column_46,
_column_47,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 mediaDownloads = Shape9(
source: i0.VersionedTable(
entityName: 'media_downloads',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_50,
_column_51,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 signalIdentityKeyStores = Shape2(
source: i0.VersionedTable(
entityName: 'signal_identity_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_31,
_column_32,
_column_33,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 signalPreKeyStores = Shape3(
source: i0.VersionedTable(
entityName: 'signal_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(pre_key_id)',
],
columns: [
_column_34,
_column_35,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape4 signalSenderKeyStores = Shape4(
source: i0.VersionedTable(
entityName: 'signal_sender_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(sender_key_name)',
],
columns: [
_column_36,
_column_37,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 signalSessionStores = Shape5(
source: i0.VersionedTable(
entityName: 'signal_session_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_31,
_column_32,
_column_38,
_column_10,
],
attachedDatabase: database,
),
alias: null);
}
class Shape10 extends i0.VersionedTable {
Shape10({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get contactId =>
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get messageId =>
columnsByName['message_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get messageOtherId =>
columnsByName['message_other_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get mediaUploadId =>
columnsByName['media_upload_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get mediaDownloadId =>
columnsByName['media_download_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get responseToMessageId =>
columnsByName['response_to_message_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get responseToOtherMessageId =>
columnsByName['response_to_other_message_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<bool> get acknowledgeByUser =>
columnsByName['acknowledge_by_user']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get mediaStored =>
columnsByName['media_stored']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<int> get downloadState =>
columnsByName['download_state']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<bool> get acknowledgeByServer =>
columnsByName['acknowledge_by_server']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get errorWhileSending =>
columnsByName['error_while_sending']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<String> get kind =>
columnsByName['kind']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get contentJson =>
columnsByName['content_json']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get openedAt =>
columnsByName['opened_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get sendAt =>
columnsByName['send_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get updatedAt =>
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<bool> _column_52(String aliasedName) =>
i1.GeneratedColumn<bool>('media_stored', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("media_stored" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
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,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4, required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5, required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@ -1054,6 +1271,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from4To5(migrator, schema); await from4To5(migrator, schema);
return 5; return 5;
case 5:
final schema = Schema6(database: database);
final migrator = i1.Migrator(database, schema);
await from5To6(migrator, schema);
return 6;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@ -1065,6 +1287,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4, required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5, required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
}) => }) =>
i0.VersionedSchema.stepByStepHelper( i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
@ -1072,4 +1295,5 @@ i1.OnUpgrade stepByStep({
from2To3: from2To3, from2To3: from2To3,
from3To4: from3To4, from3To4: from3To4,
from4To5: from4To5, from4To5: from4To5,
from5To6: from5To6,
)); ));

View file

@ -6,7 +6,7 @@ Color getMessageColorFromType(MessageContent content, BuildContext context) {
Color color; Color color;
if (content is TextMessageContent || content is StoredMediaFileContent) { if (content is TextMessageContent || content is StoredMediaFileContent) {
color = isDarkMode(context) ? Colors.white : Colors.black; color = Colors.blueAccent;
} else { } else {
if (content is MediaMessageContent) { if (content is MediaMessageContent) {
if (content.isRealTwonly) { if (content.isRealTwonly) {
@ -15,7 +15,7 @@ Color getMessageColorFromType(MessageContent content, BuildContext context) {
if (content.isVideo) { if (content.isVideo) {
color = const Color.fromARGB(255, 240, 243, 33); color = const Color.fromARGB(255, 240, 243, 33);
} else { } else {
color = Colors.deepOrange; color = Colors.redAccent;
} }
} }
} else { } else {

View file

@ -1,6 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
@ -235,7 +237,55 @@ Future<void> writeMediaFile(int mediaId, String type, Uint8List data) async {
Future<void> deleteMediaFile(int mediaId, String type) async { Future<void> deleteMediaFile(int mediaId, String type) async {
String basePath = await getMediaFilePath(mediaId, "received"); String basePath = await getMediaFilePath(mediaId, "received");
File file = File("$basePath.$type"); File file = File("$basePath.$type");
if (await file.exists()) { try {
await file.delete(); if (await file.exists()) {
await file.delete();
}
} catch (e) {
Logger("media_received.dart").shout("Erro deleting: $e");
}
}
Future<void> purgeReceivedMediaFiles() async {
final basedir = await getApplicationSupportDirectory();
final directory = Directory(join(basedir.path, 'media', "received"));
await purgeMediaFiles(directory);
}
Future<void> purgeMediaFiles(Directory directory) async {
// Check if the directory exists
if (await directory.exists()) {
// List all files in the directory
List<FileSystemEntity> files = directory.listSync();
List<int> integerFilenames = [];
// Iterate over each file
for (var file in files) {
// Get the filename
String filename = file.uri.pathSegments.last;
// Use a regular expression to extract the integer part
final match = RegExp(r'(\d+)').firstMatch(filename);
if (match != null) {
// Parse the integer and add it to the list
int messageId = int.parse(match.group(0)!);
Message? message = await twonlyDatabase.messagesDao
.getMessageByMessageId(messageId)
.getSingleOrNull();
if ((message == null) ||
(message.openedAt != null && !message.mediaStored) ||
message.errorWhileSending) {
try {
file.deleteSync();
} catch (e) {
Logger("media_received.dart").shout("$e");
}
}
}
}
print(integerFilenames);
} }
} }

View file

@ -16,6 +16,7 @@ import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart'; import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/api_utils.dart'; import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/providers/api/media_received.dart';
import 'package:twonly/src/services/notification_service.dart'; import 'package:twonly/src/services/notification_service.dart';
import 'package:video_compress/video_compress.dart'; import 'package:video_compress/video_compress.dart';
@ -45,9 +46,9 @@ Future sendMediaFile(
if (mediaUploadId != null) { if (mediaUploadId != null) {
if (videoFilePath != null) { if (videoFilePath != null) {
String basePath = await getMediaFilePath(mediaUploadId, "send"); String basePath = await getMediaFilePath(mediaUploadId, "send");
await File(videoFilePath.path).rename("$basePath.mp4"); await File(videoFilePath.path).rename("$basePath.orginal.mp4");
} }
await writeMediaFile(mediaUploadId, "png", imageBytes); await writeMediaFile(mediaUploadId, "orginal.png", imageBytes);
await handleSingleMediaFile(mediaUploadId); await handleSingleMediaFile(mediaUploadId);
} }
} }
@ -168,7 +169,7 @@ Future handleAddToMessageDb(MediaUpload media) async {
} }
Future handleCompressionState(MediaUpload media) async { Future handleCompressionState(MediaUpload media) async {
Uint8List imageBytes = await readMediaFile(media, "png"); Uint8List imageBytes = await readMediaFile(media, "orginal.png");
try { try {
Uint8List imageBytesCompressed = Uint8List imageBytesCompressed =
@ -186,18 +187,17 @@ Future handleCompressionState(MediaUpload media) async {
quality: 60, quality: 60,
); );
} }
await writeMediaFile( await writeMediaFile(media.mediaUploadId, "png", imageBytesCompressed);
media.mediaUploadId, "compressed.png", imageBytesCompressed);
} catch (e) { } catch (e) {
Logger("media_send.dart").shout("$e"); Logger("media_send.dart").shout("$e");
// as a fall back use the original image // as a fall back use the orginal image
await writeMediaFile(media.mediaUploadId, "compressed.png", imageBytes); await writeMediaFile(media.mediaUploadId, "png", imageBytes);
} }
if (media.metadata.isVideo) { if (media.metadata.isVideo) {
String basePath = await getMediaFilePath(media.mediaUploadId, "send"); String basePath = await getMediaFilePath(media.mediaUploadId, "send");
File videoOriginalFile = File("$basePath.mp4"); File videoOriginalFile = File("$basePath.orginal.mp4");
File videoCompressedFile = File("$basePath.compressed.mp4"); File videoCompressedFile = File("$basePath.mp4");
MediaInfo? mediaInfo; MediaInfo? mediaInfo;
@ -235,8 +235,8 @@ Future handleCompressionState(MediaUpload media) async {
} }
// delete non compressed media files // delete non compressed media files
await deleteMediaFile(media, "png"); await deleteMediaFile(media, "orginal.png");
await deleteMediaFile(media, "mp4"); await deleteMediaFile(media, "orginal.mp4");
await twonlyDatabase.mediaUploadsDao.updateMediaUpload( await twonlyDatabase.mediaUploadsDao.updateMediaUpload(
media.mediaUploadId, media.mediaUploadId,
@ -251,10 +251,10 @@ Future handleCompressionState(MediaUpload media) async {
Future handleEncryptionState(MediaUpload media) async { Future handleEncryptionState(MediaUpload media) async {
var state = MediaEncryptionData(); var state = MediaEncryptionData();
Uint8List dataToEncrypt = await readMediaFile(media, "compressed.png"); Uint8List dataToEncrypt = await readMediaFile(media, "png");
if (media.metadata.isVideo) { if (media.metadata.isVideo) {
Uint8List compressedVideo = await readMediaFile(media, "compressed.mp4"); Uint8List compressedVideo = await readMediaFile(media, "mp4");
dataToEncrypt = combineUint8Lists(dataToEncrypt, compressedVideo); dataToEncrypt = combineUint8Lists(dataToEncrypt, compressedVideo);
} }
@ -488,3 +488,9 @@ List<Uint8List> extractUint8Lists(Uint8List combinedList) {
combinedList.lengthInBytes - 4 - sizeOfList1); combinedList.lengthInBytes - 4 - sizeOfList1);
return [list1, list2]; return [list1, list2];
} }
Future<void> purgeSendMediaFiles() async {
final basedir = await getApplicationSupportDirectory();
final directory = Directory(join(basedir.path, 'media', "send"));
await purgeMediaFiles(directory);
}

View file

@ -108,7 +108,10 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
break; break;
case MessageKind.ack: case MessageKind.ack:
final update = MessagesCompanion(acknowledgeByUser: Value(true)); final update = MessagesCompanion(
acknowledgeByUser: Value(true),
errorWhileSending: Value(false),
);
await twonlyDatabase.messagesDao.updateMessageByOtherUser( await twonlyDatabase.messagesDao.updateMessageByOtherUser(
fromUserId, fromUserId,
message.messageId!, message.messageId!,
@ -150,6 +153,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
} }
int? responseToMessageId; int? responseToMessageId;
int? messageId;
final content = message.content!; final content = message.content!;
if (content is TextMessageContent) { if (content is TextMessageContent) {
@ -157,33 +161,40 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
} }
if (content is StoredMediaFileContent) { if (content is StoredMediaFileContent) {
responseToMessageId = content.messageId; responseToMessageId = content.messageId;
await twonlyDatabase.messagesDao.updateMessageByOtherUser(
fromUserId,
content.messageId,
MessagesCompanion(
mediaStored: Value(true),
),
);
} else {
String contentJson = jsonEncode(content.toJson());
final update = MessagesCompanion(
contactId: Value(fromUserId),
kind: Value(message.kind),
messageOtherId: Value(message.messageId),
contentJson: Value(contentJson),
acknowledgeByServer: Value(true),
acknowledgeByUser: Value(acknowledgeByUser),
responseToMessageId: Value(responseToMessageId),
openedAt: Value(openedAt),
downloadState: Value(message.kind == MessageKind.media
? DownloadState.pending
: DownloadState.downloaded),
sendAt: Value(message.timestamp),
);
messageId = await twonlyDatabase.messagesDao.insertMessage(
update,
);
if (messageId == null) {
return client.Response()..error = ErrorCode.InternalError;
}
} }
String contentJson = jsonEncode(content.toJson()); encryptAndSendMessage(
final update = MessagesCompanion(
contactId: Value(fromUserId),
kind: Value(message.kind),
messageOtherId: Value(message.messageId),
contentJson: Value(contentJson),
acknowledgeByServer: Value(true),
acknowledgeByUser: Value(acknowledgeByUser),
responseToMessageId: Value(responseToMessageId),
openedAt: Value(openedAt),
downloadState: Value(message.kind == MessageKind.media
? DownloadState.pending
: DownloadState.downloaded),
sendAt: Value(message.timestamp),
);
final messageId = await twonlyDatabase.messagesDao.insertMessage(
update,
);
if (messageId == null) {
return client.Response()..error = ErrorCode.InternalError;
}
await encryptAndSendMessage(
message.messageId!, message.messageId!,
fromUserId, fromUserId,
MessageJson( MessageJson(
@ -194,7 +205,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
), ),
); );
if (message.kind == MessageKind.media) { if (message.kind == MessageKind.media && messageId != null) {
twonlyDatabase.contactsDao.incFlameCounter( twonlyDatabase.contactsDao.incFlameCounter(
fromUserId, fromUserId,
true, true,

View file

@ -5,6 +5,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/providers/api/media_send.dart';
import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/better_text.dart'; import 'package:twonly/src/views/components/better_text.dart';
import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/initialsavatar.dart';
@ -43,6 +44,65 @@ InputDecoration inputTextMessageDeco(BuildContext context) {
); );
} }
class InChatMediaViewer extends StatefulWidget {
const InChatMediaViewer({super.key, required this.message});
final Message message;
@override
State<InChatMediaViewer> createState() => _InChatMediaViewerState();
}
class _InChatMediaViewerState extends State<InChatMediaViewer> {
File? image;
File? video;
@override
void initState() {
super.initState();
initAsync();
}
Future initAsync() async {
if (!widget.message.mediaStored) return;
bool isSend = widget.message.messageOtherId == null;
final basePath = await getMediaFilePath(
isSend ? widget.message.mediaUploadId! : widget.message.messageId,
isSend ? "send" : "received",
);
final imagePath = File("$basePath.png");
if (imagePath.existsSync()) {
setState(() {
image = imagePath;
});
} else {
print("Not found: $imagePath");
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
if (image != null) Image.file(image!),
if (image == null && video == null)
Padding(
padding: const EdgeInsets.all(10.0),
child: MessageSendStateIcon(
[widget.message],
mainAxisAlignment: MainAxisAlignment.center,
),
)
],
);
}
}
class ChatListEntry extends StatelessWidget { class ChatListEntry extends StatelessWidget {
const ChatListEntry( const ChatListEntry(
this.message, this.contact, this.lastMessageFromSameUser, this.reactions, this.message, this.contact, this.lastMessageFromSameUser, this.reactions,
@ -59,7 +119,7 @@ class ChatListEntry extends StatelessWidget {
MessageContent? content = MessageContent.fromJson( MessageContent? content = MessageContent.fromJson(
reaction.kind, jsonDecode(reaction.contentJson!)); reaction.kind, jsonDecode(reaction.contentJson!));
if (content is StoredMediaFileContent) { if (content is StoredMediaFileContent || message.mediaStored) {
children.add( children.add(
Expanded( Expanded(
child: Align( child: Align(
@ -235,7 +295,6 @@ class ChatListEntry extends StatelessWidget {
} }
}, },
child: Container( child: Container(
padding: EdgeInsets.all(10),
width: 150, width: 150,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
@ -246,9 +305,9 @@ class ChatListEntry extends StatelessWidget {
), ),
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: MessageSendStateIcon( child: ClipRRect(
[message], borderRadius: BorderRadius.circular(12),
mainAxisAlignment: MainAxisAlignment.center, child: InChatMediaViewer(message: message),
), ),
), ),
), ),
@ -293,7 +352,11 @@ class ChatListEntry extends StatelessWidget {
children: [ children: [
child, child,
Positioned( Positioned(
bottom: 5, left: 5, right: 5, child: getReactionRow()), bottom: 5,
left: 5,
right: 5,
child: getReactionRow(),
),
], ],
), ),
getTextResponseColumns(context, !right) getTextResponseColumns(context, !right)
@ -352,7 +415,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
Stream<List<Message>> msgStream = Stream<List<Message>> msgStream =
twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.contact.userId); twonlyDatabase.messagesDao.watchAllMessagesFrom(widget.contact.userId);
messageSub = msgStream.listen((msgs) { messageSub = msgStream.listen((msgs) {
if (!context.mounted) return; // if (!context.mounted) return;
if (Platform.isAndroid) { if (Platform.isAndroid) {
flutterLocalNotificationsPlugin.cancel(widget.contact.userId); flutterLocalNotificationsPlugin.cancel(widget.contact.userId);
} else { } else {
@ -481,6 +544,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
reactions = reactionsToOtherMessages[msg.messageOtherId!]!; reactions = reactionsToOtherMessages[msg.messageOtherId!]!;
} }
return ChatListEntry( return ChatListEntry(
key: Key(msg.messageId.toString()),
msg, msg,
user, user,
lastMessageFromSameUser, lastMessageFromSameUser,

View file

@ -100,6 +100,16 @@ class _MediaViewerViewState extends State<MediaViewerView> {
Future nextMediaOrExit() async { Future nextMediaOrExit() async {
nextMediaTimer?.cancel(); nextMediaTimer?.cancel();
progressTimer?.cancel(); progressTimer?.cancel();
if (allMediaFiles.isNotEmpty) {
try {
if (!imageSaved) {
await deleteMediaFile(allMediaFiles.first.messageId, "mp4");
await deleteMediaFile(allMediaFiles.first.messageId, "png");
}
} catch (e) {
Logger("media_viewer_view.dart").shout("$e");
}
}
if (allMediaFiles.isEmpty || allMediaFiles.length == 1) { if (allMediaFiles.isEmpty || allMediaFiles.length == 1) {
if (context.mounted) { if (context.mounted) {
Navigator.pop(context); Navigator.pop(context);
@ -268,6 +278,10 @@ class _MediaViewerViewState extends State<MediaViewerView> {
setState(() { setState(() {
imageSaving = true; imageSaving = true;
}); });
await twonlyDatabase.messagesDao.updateMessageByMessageId(
allMediaFiles.first.messageId,
MessagesCompanion(mediaStored: Value(true)),
);
encryptAndSendMessage( encryptAndSendMessage(
null, null,
widget.contact.userId, widget.contact.userId,
@ -281,11 +295,13 @@ class _MediaViewerViewState extends State<MediaViewerView> {
), ),
pushKind: PushKind.storedMediaFile, pushKind: PushKind.storedMediaFile,
); );
setState(() {
imageSaved = true;
});
final res = await saveImageToGallery(imageBytes!); final res = await saveImageToGallery(imageBytes!);
if (res == null) { if (res == null) {
setState(() { setState(() {
imageSaving = false; imageSaving = false;
imageSaved = true;
}); });
} }
} }
@ -295,7 +311,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (maxShowTime == gMediaShowInfinite) if (maxShowTime == gMediaShowInfinite && videoController == null)
OutlinedButton( OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
iconColor: imageSaved iconColor: imageSaved

View file

@ -8,6 +8,7 @@ import 'schema_v2.dart' as v2;
import 'schema_v3.dart' as v3; import 'schema_v3.dart' as v3;
import 'schema_v4.dart' as v4; import 'schema_v4.dart' as v4;
import 'schema_v5.dart' as v5; import 'schema_v5.dart' as v5;
import 'schema_v6.dart' as v6;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@ -23,10 +24,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v4.DatabaseAtV4(db); return v4.DatabaseAtV4(db);
case 5: case 5:
return v5.DatabaseAtV5(db); return v5.DatabaseAtV5(db);
case 6:
return v6.DatabaseAtV6(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
} }
static const versions = const [1, 2, 3, 4, 5]; static const versions = const [1, 2, 3, 4, 5, 6];
} }

File diff suppressed because it is too large Load diff