This commit is contained in:
otsmr 2025-03-30 19:51:57 +02:00
parent f559e643b1
commit 875ebb9eab
33 changed files with 5168 additions and 107 deletions

13
build.yaml Normal file
View file

@ -0,0 +1,13 @@
targets:
$default:
sources:
- lib/**
- "tool/**"
- pubspec.yaml
- lib/$lib$
- $package$
builders:
drift_dev:
options:
databases:
twonly_database: lib/src/database/twonly_database.dart

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,7 @@ import 'dart:collection';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/components/flame.dart'; import 'package:twonly/src/components/flame.dart';

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/json_models/userdata.dart'; import 'package:twonly/src/json_models/userdata.dart';
@ -44,9 +44,6 @@ class ContactAvatar extends StatelessWidget {
height: proSize, height: proSize,
width: proSize, width: proSize,
child: Center( child: Center(
// child: Container(
// color: Colors.green,
// ),
child: SvgPicture.string( child: SvgPicture.string(
avatarSvg, avatarSvg,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {

View file

@ -143,7 +143,13 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
if (message.kind == MessageKind.storedMediaFile) { if (message.kind == MessageKind.storedMediaFile) {
icon = FaIcon(FontAwesomeIcons.floppyDisk, size: 12, color: color); icon = FaIcon(FontAwesomeIcons.floppyDisk, size: 12, color: color);
text = "Stored in gallery"; text = context.lang.messageStoredInGalery;
}
if (message.errorWhileSending) {
icon =
FaIcon(FontAwesomeIcons.circleExclamation, size: 12, color: color);
text = "Error while sending";
} }
icons.add(icon); icons.add(icon);

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class PermissionHandlerView extends StatefulWidget { class PermissionHandlerView extends StatefulWidget {
@ -23,12 +24,14 @@ Future<bool> checkPermissions() async {
class PermissionHandlerViewState extends State<PermissionHandlerView> { class PermissionHandlerViewState extends State<PermissionHandlerView> {
Future<Map<Permission, PermissionStatus>> permissionServices() async { Future<Map<Permission, PermissionStatus>> permissionServices() async {
// You can request multiple permissions at once. // try {
Map<Permission, PermissionStatus> statuses = await [ Map<Permission, PermissionStatus> statuses = await [
Permission.camera, Permission.camera,
// Permission.microphone, // Permission.microphone,
Permission.notification Permission.notification
].request(); ].request();
// } catch (e) {}
// You can request multiple permissions at once.
// if (statuses[Permission.microphone]!.isPermanentlyDenied) { // if (statuses[Permission.microphone]!.isPermanentlyDenied) {
// openAppSettings(); // openAppSettings();
@ -70,10 +73,14 @@ class PermissionHandlerViewState extends State<PermissionHandlerView> {
label: Text("Request permissions"), label: Text("Request permissions"),
icon: const Icon(Icons.perm_camera_mic), icon: const Icon(Icons.perm_camera_mic),
onPressed: () async { onPressed: () async {
try {
permissionServices(); permissionServices();
if (await checkPermissions()) { if (await checkPermissions()) {
widget.onSuccess(); widget.onSuccess();
} }
} catch (e) {
Logger("permissions_view").shout(e);
}
}, },
), ),
], ],

View file

@ -164,3 +164,28 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
}); });
} }
} }
String getContactDisplayName(Contact user) {
if (user.nickName != null) {
return user.nickName!;
}
if (user.displayName != null) {
return user.displayName!;
}
return user.username;
}
int getFlameCounterFromContact(Contact contact) {
if (contact.lastMessageSend == null || contact.lastMessageReceived == null) {
return 0;
}
final now = DateTime.now();
final startOfToday = DateTime(now.year, now.month, now.day);
final twoDaysAgo = startOfToday.subtract(Duration(days: 2));
if (contact.lastMessageSend!.isAfter(twoDaysAgo) &&
contact.lastMessageReceived!.isAfter(twoDaysAgo)) {
return contact.flameCounter + 1;
} else {
return 0;
}
}

View file

@ -1,13 +1,12 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/json_models/message.dart';
part 'messages_dao.g.dart'; part 'messages_dao.g.dart';
@DriftAccessor(tables: [Messages]) @DriftAccessor(tables: [Messages, Contacts])
class MessagesDao extends DatabaseAccessor<TwonlyDatabase> class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
with _$MessagesDaoMixin { with _$MessagesDaoMixin {
// this constructor is required so that the main database can create an instance // this constructor is required so that the main database can create an instance
@ -26,6 +25,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
..where((t) => ..where((t) =>
t.openedAt.isNull() & t.openedAt.isNull() &
t.contactId.equals(contactId) & t.contactId.equals(contactId) &
t.errorWhileSending.equals(false) &
t.messageOtherId.isNotNull() & t.messageOtherId.isNotNull() &
t.kind.equals(MessageKind.media.name)) t.kind.equals(MessageKind.media.name))
..orderBy([(t) => OrderingTerm.asc(t.sendAt)])) ..orderBy([(t) => OrderingTerm.asc(t.sendAt)]))
@ -72,8 +72,17 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
.get(); .get();
} }
Future<List<Message>> getAllMessagesForRetransmitting() { Future<List<Message>> getAllMessagesPendingUploadOlderThanAMinute() {
return (select(messages)..where((t) => t.acknowledgeByServer.equals(false))) return (select(messages)
..where(
(t) =>
t.acknowledgeByServer.equals(false) &
t.messageOtherId.isNull() &
t.errorWhileSending.equals(false) &
t.sendAt.isSmallerThanValue(
DateTime.now().subtract(Duration(minutes: 5))) &
t.kind.equals(MessageKind.media.name),
))
.get(); .get();
} }
@ -113,8 +122,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
Future<int?> insertMessage(MessagesCompanion message) async { Future<int?> insertMessage(MessagesCompanion message) async {
try { try {
await twonlyDatabase.contactsDao await (update(contacts)
.newMessageExchange(message.contactId.value); ..where((c) => c.userId.equals(message.contactId.value)))
.write(ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
return await into(messages).insert(message); return await into(messages).insert(message);
} catch (e) { } catch (e) {
Logger("twonlyDatabase").shout("Error while inserting message: $e"); Logger("twonlyDatabase").shout("Error while inserting message: $e");

View file

@ -1,5 +1,4 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:twonly/src/database/twonly_database.dart';
class Contacts extends Table { class Contacts extends Table {
IntColumn get userId => integer()(); IntColumn get userId => integer()();
@ -31,28 +30,3 @@ class Contacts extends Table {
@override @override
Set<Column> get primaryKey => {userId}; Set<Column> get primaryKey => {userId};
} }
String getContactDisplayName(Contact user) {
if (user.nickName != null) {
return user.nickName!;
}
if (user.displayName != null) {
return user.displayName!;
}
return user.username;
}
int getFlameCounterFromContact(Contact contact) {
if (contact.lastMessageSend == null || contact.lastMessageReceived == null) {
return 0;
}
final now = DateTime.now();
final startOfToday = DateTime(now.year, now.month, now.day);
final twoDaysAgo = startOfToday.subtract(Duration(days: 2));
if (contact.lastMessageSend!.isAfter(twoDaysAgo) &&
contact.lastMessageReceived!.isAfter(twoDaysAgo)) {
return contact.flameCounter + 1;
} else {
return 0;
}
}

View file

@ -1,6 +1,17 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/json_models/message.dart';
enum MessageKind {
textMessage,
storedMediaFile,
media,
contactRequest,
profileChange,
rejectRequest,
acceptRequest,
opened,
ack
}
enum DownloadState { enum DownloadState {
pending, pending,
@ -25,6 +36,8 @@ class Messages extends Table {
BoolColumn get acknowledgeByServer => BoolColumn get acknowledgeByServer =>
boolean().withDefault(Constant(false))(); boolean().withDefault(Constant(false))();
BoolColumn get errorWhileSending => boolean().withDefault(Constant(false))();
TextColumn get kind => textEnum<MessageKind>()(); TextColumn get kind => textEnum<MessageKind>()();
TextColumn get contentJson => text().nullable()(); TextColumn get contentJson => text().nullable()();

View file

@ -1,5 +1,6 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart'; import 'package:drift_flutter/drift_flutter.dart'
show driftDatabase, DriftNativeOptions;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/daos/messages_dao.dart'; import 'package:twonly/src/database/daos/messages_dao.dart';
@ -9,7 +10,7 @@ import 'package:twonly/src/database/tables/signal_identity_key_store_table.dart'
import 'package:twonly/src/database/tables/signal_pre_key_store_table.dart'; import 'package:twonly/src/database/tables/signal_pre_key_store_table.dart';
import 'package:twonly/src/database/tables/signal_sender_key_store_table.dart'; import 'package:twonly/src/database/tables/signal_sender_key_store_table.dart';
import 'package:twonly/src/database/tables/signal_session_store_table.dart'; import 'package:twonly/src/database/tables/signal_session_store_table.dart';
import 'package:twonly/src/json_models/message.dart'; import 'package:twonly/src/database/twonly_database.steps.dart';
part 'twonly_database.g.dart'; part 'twonly_database.g.dart';
@ -26,10 +27,15 @@ part 'twonly_database.g.dart';
ContactsDao ContactsDao
]) ])
class TwonlyDatabase extends _$TwonlyDatabase { class TwonlyDatabase extends _$TwonlyDatabase {
TwonlyDatabase() : super(_openConnection()); TwonlyDatabase([QueryExecutor? e])
: super(
e ?? _openConnection(),
);
TwonlyDatabase.forTesting(DatabaseConnection super.connection);
@override @override
int get schemaVersion => 1; int get schemaVersion => 2;
static QueryExecutor _openConnection() { static QueryExecutor _openConnection() {
return driftDatabase( return driftDatabase(
@ -40,6 +46,15 @@ class TwonlyDatabase extends _$TwonlyDatabase {
); );
} }
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: stepByStep(from1To2: (m, schema) async {
m.addColumn(schema.messages, schema.messages.errorWhileSending);
}),
);
}
void markUpdated() { void markUpdated() {
notifyUpdates({TableUpdate.onTable(messages, kind: UpdateKind.update)}); notifyUpdates({TableUpdate.onTable(messages, kind: UpdateKind.update)});
notifyUpdates({TableUpdate.onTable(contacts, kind: UpdateKind.update)}); notifyUpdates({TableUpdate.onTable(contacts, kind: UpdateKind.update)});

View file

@ -904,6 +904,16 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
defaultConstraints: GeneratedColumn.constraintIsAlways( defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("acknowledge_by_server" IN (0, 1))'), 'CHECK ("acknowledge_by_server" IN (0, 1))'),
defaultValue: Constant(false)); defaultValue: Constant(false));
static const VerificationMeta _errorWhileSendingMeta =
const VerificationMeta('errorWhileSending');
@override
late final GeneratedColumn<bool> errorWhileSending = GeneratedColumn<bool>(
'error_while_sending', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("error_while_sending" IN (0, 1))'),
defaultValue: Constant(false));
@override @override
late final GeneratedColumnWithTypeConverter<MessageKind, String> kind = late final GeneratedColumnWithTypeConverter<MessageKind, String> kind =
GeneratedColumn<String>('kind', aliasedName, false, GeneratedColumn<String>('kind', aliasedName, false,
@ -946,6 +956,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
acknowledgeByUser, acknowledgeByUser,
downloadState, downloadState,
acknowledgeByServer, acknowledgeByServer,
errorWhileSending,
kind, kind,
contentJson, contentJson,
openedAt, openedAt,
@ -1003,6 +1014,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
acknowledgeByServer.isAcceptableOrUnknown( acknowledgeByServer.isAcceptableOrUnknown(
data['acknowledge_by_server']!, _acknowledgeByServerMeta)); data['acknowledge_by_server']!, _acknowledgeByServerMeta));
} }
if (data.containsKey('error_while_sending')) {
context.handle(
_errorWhileSendingMeta,
errorWhileSending.isAcceptableOrUnknown(
data['error_while_sending']!, _errorWhileSendingMeta));
}
if (data.containsKey('content_json')) { if (data.containsKey('content_json')) {
context.handle( context.handle(
_contentJsonMeta, _contentJsonMeta,
@ -1048,6 +1065,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
DriftSqlType.int, data['${effectivePrefix}download_state'])!), DriftSqlType.int, data['${effectivePrefix}download_state'])!),
acknowledgeByServer: attachedDatabase.typeMapping.read( acknowledgeByServer: attachedDatabase.typeMapping.read(
DriftSqlType.bool, data['${effectivePrefix}acknowledge_by_server'])!, DriftSqlType.bool, data['${effectivePrefix}acknowledge_by_server'])!,
errorWhileSending: attachedDatabase.typeMapping.read(
DriftSqlType.bool, data['${effectivePrefix}error_while_sending'])!,
kind: $MessagesTable.$converterkind.fromSql(attachedDatabase.typeMapping kind: $MessagesTable.$converterkind.fromSql(attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}kind'])!), .read(DriftSqlType.string, data['${effectivePrefix}kind'])!),
contentJson: attachedDatabase.typeMapping contentJson: attachedDatabase.typeMapping
@ -1081,6 +1100,7 @@ class Message extends DataClass implements Insertable<Message> {
final bool acknowledgeByUser; final bool acknowledgeByUser;
final DownloadState downloadState; final DownloadState downloadState;
final bool acknowledgeByServer; final bool acknowledgeByServer;
final bool errorWhileSending;
final MessageKind kind; final MessageKind kind;
final String? contentJson; final String? contentJson;
final DateTime? openedAt; final DateTime? openedAt;
@ -1095,6 +1115,7 @@ class Message extends DataClass implements Insertable<Message> {
required this.acknowledgeByUser, required this.acknowledgeByUser,
required this.downloadState, required this.downloadState,
required this.acknowledgeByServer, required this.acknowledgeByServer,
required this.errorWhileSending,
required this.kind, required this.kind,
this.contentJson, this.contentJson,
this.openedAt, this.openedAt,
@ -1121,6 +1142,7 @@ class Message extends DataClass implements Insertable<Message> {
$MessagesTable.$converterdownloadState.toSql(downloadState)); $MessagesTable.$converterdownloadState.toSql(downloadState));
} }
map['acknowledge_by_server'] = Variable<bool>(acknowledgeByServer); map['acknowledge_by_server'] = Variable<bool>(acknowledgeByServer);
map['error_while_sending'] = Variable<bool>(errorWhileSending);
{ {
map['kind'] = Variable<String>($MessagesTable.$converterkind.toSql(kind)); map['kind'] = Variable<String>($MessagesTable.$converterkind.toSql(kind));
} }
@ -1151,6 +1173,7 @@ class Message extends DataClass implements Insertable<Message> {
acknowledgeByUser: Value(acknowledgeByUser), acknowledgeByUser: Value(acknowledgeByUser),
downloadState: Value(downloadState), downloadState: Value(downloadState),
acknowledgeByServer: Value(acknowledgeByServer), acknowledgeByServer: Value(acknowledgeByServer),
errorWhileSending: Value(errorWhileSending),
kind: Value(kind), kind: Value(kind),
contentJson: contentJson == null && nullToAbsent contentJson: contentJson == null && nullToAbsent
? const Value.absent() ? const Value.absent()
@ -1179,6 +1202,7 @@ class Message extends DataClass implements Insertable<Message> {
.fromJson(serializer.fromJson<int>(json['downloadState'])), .fromJson(serializer.fromJson<int>(json['downloadState'])),
acknowledgeByServer: acknowledgeByServer:
serializer.fromJson<bool>(json['acknowledgeByServer']), serializer.fromJson<bool>(json['acknowledgeByServer']),
errorWhileSending: serializer.fromJson<bool>(json['errorWhileSending']),
kind: $MessagesTable.$converterkind kind: $MessagesTable.$converterkind
.fromJson(serializer.fromJson<String>(json['kind'])), .fromJson(serializer.fromJson<String>(json['kind'])),
contentJson: serializer.fromJson<String?>(json['contentJson']), contentJson: serializer.fromJson<String?>(json['contentJson']),
@ -1201,6 +1225,7 @@ class Message extends DataClass implements Insertable<Message> {
'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),
'errorWhileSending': serializer.toJson<bool>(errorWhileSending),
'kind': 'kind':
serializer.toJson<String>($MessagesTable.$converterkind.toJson(kind)), serializer.toJson<String>($MessagesTable.$converterkind.toJson(kind)),
'contentJson': serializer.toJson<String?>(contentJson), 'contentJson': serializer.toJson<String?>(contentJson),
@ -1219,6 +1244,7 @@ class Message extends DataClass implements Insertable<Message> {
bool? acknowledgeByUser, bool? acknowledgeByUser,
DownloadState? downloadState, DownloadState? downloadState,
bool? acknowledgeByServer, bool? acknowledgeByServer,
bool? errorWhileSending,
MessageKind? kind, MessageKind? kind,
Value<String?> contentJson = const Value.absent(), Value<String?> contentJson = const Value.absent(),
Value<DateTime?> openedAt = const Value.absent(), Value<DateTime?> openedAt = const Value.absent(),
@ -1238,6 +1264,7 @@ class Message extends DataClass implements Insertable<Message> {
acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser, acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser,
downloadState: downloadState ?? this.downloadState, downloadState: downloadState ?? this.downloadState,
acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer, acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer,
errorWhileSending: errorWhileSending ?? this.errorWhileSending,
kind: kind ?? this.kind, kind: kind ?? this.kind,
contentJson: contentJson.present ? contentJson.value : this.contentJson, contentJson: contentJson.present ? contentJson.value : this.contentJson,
openedAt: openedAt.present ? openedAt.value : this.openedAt, openedAt: openedAt.present ? openedAt.value : this.openedAt,
@ -1266,6 +1293,9 @@ class Message extends DataClass implements Insertable<Message> {
acknowledgeByServer: data.acknowledgeByServer.present acknowledgeByServer: data.acknowledgeByServer.present
? data.acknowledgeByServer.value ? data.acknowledgeByServer.value
: this.acknowledgeByServer, : this.acknowledgeByServer,
errorWhileSending: data.errorWhileSending.present
? data.errorWhileSending.value
: this.errorWhileSending,
kind: data.kind.present ? data.kind.value : this.kind, kind: data.kind.present ? data.kind.value : this.kind,
contentJson: contentJson:
data.contentJson.present ? data.contentJson.value : this.contentJson, data.contentJson.present ? data.contentJson.value : this.contentJson,
@ -1286,6 +1316,7 @@ class Message extends DataClass implements Insertable<Message> {
..write('acknowledgeByUser: $acknowledgeByUser, ') ..write('acknowledgeByUser: $acknowledgeByUser, ')
..write('downloadState: $downloadState, ') ..write('downloadState: $downloadState, ')
..write('acknowledgeByServer: $acknowledgeByServer, ') ..write('acknowledgeByServer: $acknowledgeByServer, ')
..write('errorWhileSending: $errorWhileSending, ')
..write('kind: $kind, ') ..write('kind: $kind, ')
..write('contentJson: $contentJson, ') ..write('contentJson: $contentJson, ')
..write('openedAt: $openedAt, ') ..write('openedAt: $openedAt, ')
@ -1305,6 +1336,7 @@ class Message extends DataClass implements Insertable<Message> {
acknowledgeByUser, acknowledgeByUser,
downloadState, downloadState,
acknowledgeByServer, acknowledgeByServer,
errorWhileSending,
kind, kind,
contentJson, contentJson,
openedAt, openedAt,
@ -1322,6 +1354,7 @@ class Message extends DataClass implements Insertable<Message> {
other.acknowledgeByUser == this.acknowledgeByUser && other.acknowledgeByUser == this.acknowledgeByUser &&
other.downloadState == this.downloadState && other.downloadState == this.downloadState &&
other.acknowledgeByServer == this.acknowledgeByServer && other.acknowledgeByServer == this.acknowledgeByServer &&
other.errorWhileSending == this.errorWhileSending &&
other.kind == this.kind && other.kind == this.kind &&
other.contentJson == this.contentJson && other.contentJson == this.contentJson &&
other.openedAt == this.openedAt && other.openedAt == this.openedAt &&
@ -1338,6 +1371,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
final Value<bool> acknowledgeByUser; final Value<bool> acknowledgeByUser;
final Value<DownloadState> downloadState; final Value<DownloadState> downloadState;
final Value<bool> acknowledgeByServer; final Value<bool> acknowledgeByServer;
final Value<bool> errorWhileSending;
final Value<MessageKind> kind; final Value<MessageKind> kind;
final Value<String?> contentJson; final Value<String?> contentJson;
final Value<DateTime?> openedAt; final Value<DateTime?> openedAt;
@ -1352,6 +1386,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
this.acknowledgeByUser = const Value.absent(), this.acknowledgeByUser = 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.kind = const Value.absent(), this.kind = const Value.absent(),
this.contentJson = const Value.absent(), this.contentJson = const Value.absent(),
this.openedAt = const Value.absent(), this.openedAt = const Value.absent(),
@ -1367,6 +1402,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
this.acknowledgeByUser = const Value.absent(), this.acknowledgeByUser = 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(),
required MessageKind kind, required MessageKind kind,
this.contentJson = const Value.absent(), this.contentJson = const Value.absent(),
this.openedAt = const Value.absent(), this.openedAt = const Value.absent(),
@ -1383,6 +1419,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
Expression<bool>? acknowledgeByUser, Expression<bool>? acknowledgeByUser,
Expression<int>? downloadState, Expression<int>? downloadState,
Expression<bool>? acknowledgeByServer, Expression<bool>? acknowledgeByServer,
Expression<bool>? errorWhileSending,
Expression<String>? kind, Expression<String>? kind,
Expression<String>? contentJson, Expression<String>? contentJson,
Expression<DateTime>? openedAt, Expression<DateTime>? openedAt,
@ -1401,6 +1438,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
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,
if (errorWhileSending != null) 'error_while_sending': errorWhileSending,
if (kind != null) 'kind': kind, if (kind != null) 'kind': kind,
if (contentJson != null) 'content_json': contentJson, if (contentJson != null) 'content_json': contentJson,
if (openedAt != null) 'opened_at': openedAt, if (openedAt != null) 'opened_at': openedAt,
@ -1418,6 +1456,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
Value<bool>? acknowledgeByUser, Value<bool>? acknowledgeByUser,
Value<DownloadState>? downloadState, Value<DownloadState>? downloadState,
Value<bool>? acknowledgeByServer, Value<bool>? acknowledgeByServer,
Value<bool>? errorWhileSending,
Value<MessageKind>? kind, Value<MessageKind>? kind,
Value<String?>? contentJson, Value<String?>? contentJson,
Value<DateTime?>? openedAt, Value<DateTime?>? openedAt,
@ -1433,6 +1472,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser, acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser,
downloadState: downloadState ?? this.downloadState, downloadState: downloadState ?? this.downloadState,
acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer, acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer,
errorWhileSending: errorWhileSending ?? this.errorWhileSending,
kind: kind ?? this.kind, kind: kind ?? this.kind,
contentJson: contentJson ?? this.contentJson, contentJson: contentJson ?? this.contentJson,
openedAt: openedAt ?? this.openedAt, openedAt: openedAt ?? this.openedAt,
@ -1470,6 +1510,9 @@ class MessagesCompanion extends UpdateCompanion<Message> {
if (acknowledgeByServer.present) { if (acknowledgeByServer.present) {
map['acknowledge_by_server'] = Variable<bool>(acknowledgeByServer.value); map['acknowledge_by_server'] = Variable<bool>(acknowledgeByServer.value);
} }
if (errorWhileSending.present) {
map['error_while_sending'] = Variable<bool>(errorWhileSending.value);
}
if (kind.present) { if (kind.present) {
map['kind'] = map['kind'] =
Variable<String>($MessagesTable.$converterkind.toSql(kind.value)); Variable<String>($MessagesTable.$converterkind.toSql(kind.value));
@ -1500,6 +1543,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
..write('acknowledgeByUser: $acknowledgeByUser, ') ..write('acknowledgeByUser: $acknowledgeByUser, ')
..write('downloadState: $downloadState, ') ..write('downloadState: $downloadState, ')
..write('acknowledgeByServer: $acknowledgeByServer, ') ..write('acknowledgeByServer: $acknowledgeByServer, ')
..write('errorWhileSending: $errorWhileSending, ')
..write('kind: $kind, ') ..write('kind: $kind, ')
..write('contentJson: $contentJson, ') ..write('contentJson: $contentJson, ')
..write('openedAt: $openedAt, ') ..write('openedAt: $openedAt, ')
@ -2966,6 +3010,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
Value<bool> acknowledgeByUser, Value<bool> acknowledgeByUser,
Value<DownloadState> downloadState, Value<DownloadState> downloadState,
Value<bool> acknowledgeByServer, Value<bool> acknowledgeByServer,
Value<bool> errorWhileSending,
required MessageKind kind, required MessageKind kind,
Value<String?> contentJson, Value<String?> contentJson,
Value<DateTime?> openedAt, Value<DateTime?> openedAt,
@ -2981,6 +3026,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
Value<bool> acknowledgeByUser, Value<bool> acknowledgeByUser,
Value<DownloadState> downloadState, Value<DownloadState> downloadState,
Value<bool> acknowledgeByServer, Value<bool> acknowledgeByServer,
Value<bool> errorWhileSending,
Value<MessageKind> kind, Value<MessageKind> kind,
Value<String?> contentJson, Value<String?> contentJson,
Value<DateTime?> openedAt, Value<DateTime?> openedAt,
@ -3045,6 +3091,10 @@ class $$MessagesTableFilterComposer
column: $table.acknowledgeByServer, column: $table.acknowledgeByServer,
builder: (column) => ColumnFilters(column)); builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get errorWhileSending => $composableBuilder(
column: $table.errorWhileSending,
builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<MessageKind, MessageKind, String> get kind => ColumnWithTypeConverterFilters<MessageKind, MessageKind, String> get kind =>
$composableBuilder( $composableBuilder(
column: $table.kind, column: $table.kind,
@ -3119,6 +3169,10 @@ class $$MessagesTableOrderingComposer
column: $table.acknowledgeByServer, column: $table.acknowledgeByServer,
builder: (column) => ColumnOrderings(column)); builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get errorWhileSending => $composableBuilder(
column: $table.errorWhileSending,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get kind => $composableBuilder( ColumnOrderings<String> get kind => $composableBuilder(
column: $table.kind, builder: (column) => ColumnOrderings(column)); column: $table.kind, builder: (column) => ColumnOrderings(column));
@ -3186,6 +3240,9 @@ class $$MessagesTableAnnotationComposer
GeneratedColumn<bool> get acknowledgeByServer => $composableBuilder( GeneratedColumn<bool> get acknowledgeByServer => $composableBuilder(
column: $table.acknowledgeByServer, builder: (column) => column); column: $table.acknowledgeByServer, builder: (column) => column);
GeneratedColumn<bool> get errorWhileSending => $composableBuilder(
column: $table.errorWhileSending, builder: (column) => column);
GeneratedColumnWithTypeConverter<MessageKind, String> get kind => GeneratedColumnWithTypeConverter<MessageKind, String> get kind =>
$composableBuilder(column: $table.kind, builder: (column) => column); $composableBuilder(column: $table.kind, builder: (column) => column);
@ -3253,6 +3310,7 @@ class $$MessagesTableTableManager extends RootTableManager<
Value<bool> acknowledgeByUser = const Value.absent(), Value<bool> acknowledgeByUser = 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<MessageKind> kind = const Value.absent(), Value<MessageKind> kind = const Value.absent(),
Value<String?> contentJson = const Value.absent(), Value<String?> contentJson = const Value.absent(),
Value<DateTime?> openedAt = const Value.absent(), Value<DateTime?> openedAt = const Value.absent(),
@ -3268,6 +3326,7 @@ class $$MessagesTableTableManager extends RootTableManager<
acknowledgeByUser: acknowledgeByUser, acknowledgeByUser: acknowledgeByUser,
downloadState: downloadState, downloadState: downloadState,
acknowledgeByServer: acknowledgeByServer, acknowledgeByServer: acknowledgeByServer,
errorWhileSending: errorWhileSending,
kind: kind, kind: kind,
contentJson: contentJson, contentJson: contentJson,
openedAt: openedAt, openedAt: openedAt,
@ -3283,6 +3342,7 @@ class $$MessagesTableTableManager extends RootTableManager<
Value<bool> acknowledgeByUser = const Value.absent(), Value<bool> acknowledgeByUser = 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(),
required MessageKind kind, required MessageKind kind,
Value<String?> contentJson = const Value.absent(), Value<String?> contentJson = const Value.absent(),
Value<DateTime?> openedAt = const Value.absent(), Value<DateTime?> openedAt = const Value.absent(),
@ -3298,6 +3358,7 @@ class $$MessagesTableTableManager extends RootTableManager<
acknowledgeByUser: acknowledgeByUser, acknowledgeByUser: acknowledgeByUser,
downloadState: downloadState, downloadState: downloadState,
acknowledgeByServer: acknowledgeByServer, acknowledgeByServer: acknowledgeByServer,
errorWhileSending: errorWhileSending,
kind: kind, kind: kind,
contentJson: contentJson, contentJson: contentJson,
openedAt: openedAt, openedAt: openedAt,

View file

@ -0,0 +1,433 @@
// dart format width=80
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'dart:typed_data' as i2;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
// GENERATED BY drift_dev, DO NOT MODIFY.
final class Schema2 extends i0.VersionedSchema {
Schema2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
messages,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
];
late final Shape0 contacts = Shape0(
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_10,
_column_11,
_column_12,
_column_13,
_column_14,
_column_15,
_column_16,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 messages = Shape1(
source: i0.VersionedTable(
entityName: 'messages',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
_column_22,
_column_23,
_column_24,
_column_25,
_column_26,
_column_27,
_column_28,
_column_29,
_column_30,
],
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 Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get userId =>
columnsByName['user_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get username =>
columnsByName['username']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get displayName =>
columnsByName['display_name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get nickName =>
columnsByName['nick_name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get avatarSvg =>
columnsByName['avatar_svg']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get myAvatarCounter =>
columnsByName['my_avatar_counter']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<bool> get accepted =>
columnsByName['accepted']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get requested =>
columnsByName['requested']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get blocked =>
columnsByName['blocked']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get verified =>
columnsByName['verified']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get totalMediaCounter =>
columnsByName['total_media_counter']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get lastMessageSend =>
columnsByName['last_message_send']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get lastMessageReceived =>
columnsByName['last_message_received']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get lastFlameCounterChange =>
columnsByName['last_flame_counter_change']!
as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get lastMessageExchange =>
columnsByName['last_message_exchange']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get flameCounter =>
columnsByName['flame_counter']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_0(String aliasedName) =>
i1.GeneratedColumn<int>('user_id', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>('username', aliasedName, false,
type: i1.DriftSqlType.string,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways('UNIQUE'));
i1.GeneratedColumn<String> _column_2(String aliasedName) =>
i1.GeneratedColumn<String>('display_name', aliasedName, true,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
i1.GeneratedColumn<String>('nick_name', aliasedName, true,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String> _column_4(String aliasedName) =>
i1.GeneratedColumn<String>('avatar_svg', aliasedName, true,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<int> _column_5(String aliasedName) =>
i1.GeneratedColumn<int>('my_avatar_counter', aliasedName, false,
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<bool> _column_6(String aliasedName) =>
i1.GeneratedColumn<bool>('accepted', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("accepted" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<bool> _column_7(String aliasedName) =>
i1.GeneratedColumn<bool>('requested', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("requested" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<bool> _column_8(String aliasedName) =>
i1.GeneratedColumn<bool>('blocked', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("blocked" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<bool> _column_9(String aliasedName) =>
i1.GeneratedColumn<bool>('verified', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("verified" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<DateTime> _column_10(String aliasedName) =>
i1.GeneratedColumn<DateTime>('created_at', aliasedName, false,
type: i1.DriftSqlType.dateTime,
defaultValue: const CustomExpression(
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
i1.GeneratedColumn<int> _column_11(String aliasedName) =>
i1.GeneratedColumn<int>('total_media_counter', aliasedName, false,
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<DateTime> _column_12(String aliasedName) =>
i1.GeneratedColumn<DateTime>('last_message_send', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i1.GeneratedColumn<DateTime> _column_13(String aliasedName) =>
i1.GeneratedColumn<DateTime>('last_message_received', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i1.GeneratedColumn<DateTime> _column_14(String aliasedName) =>
i1.GeneratedColumn<DateTime>('last_flame_counter_change', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i1.GeneratedColumn<DateTime> _column_15(String aliasedName) =>
i1.GeneratedColumn<DateTime>('last_message_exchange', aliasedName, false,
type: i1.DriftSqlType.dateTime,
defaultValue: const CustomExpression(
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
i1.GeneratedColumn<int> _column_16(String aliasedName) =>
i1.GeneratedColumn<int>('flame_counter', aliasedName, false,
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('0'));
class Shape1 extends i0.VersionedTable {
Shape1({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 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<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<int> _column_17(String aliasedName) =>
i1.GeneratedColumn<int>('contact_id', aliasedName, false,
type: i1.DriftSqlType.int,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'REFERENCES contacts (user_id)'));
i1.GeneratedColumn<int> _column_18(String aliasedName) =>
i1.GeneratedColumn<int>('message_id', aliasedName, false,
hasAutoIncrement: true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
i1.GeneratedColumn<int> _column_19(String aliasedName) =>
i1.GeneratedColumn<int>('message_other_id', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_20(String aliasedName) =>
i1.GeneratedColumn<int>('response_to_message_id', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_21(String aliasedName) =>
i1.GeneratedColumn<int>('response_to_other_message_id', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<bool> _column_22(String aliasedName) =>
i1.GeneratedColumn<bool>('acknowledge_by_user', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("acknowledge_by_user" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<int> _column_23(String aliasedName) =>
i1.GeneratedColumn<int>('download_state', aliasedName, false,
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('2'));
i1.GeneratedColumn<bool> _column_24(String aliasedName) =>
i1.GeneratedColumn<bool>('acknowledge_by_server', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("acknowledge_by_server" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<bool> _column_25(String aliasedName) =>
i1.GeneratedColumn<bool>('error_while_sending', aliasedName, false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("error_while_sending" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<String> _column_26(String aliasedName) =>
i1.GeneratedColumn<String>('kind', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String> _column_27(String aliasedName) =>
i1.GeneratedColumn<String>('content_json', aliasedName, true,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<DateTime> _column_28(String aliasedName) =>
i1.GeneratedColumn<DateTime>('opened_at', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i1.GeneratedColumn<DateTime> _column_29(String aliasedName) =>
i1.GeneratedColumn<DateTime>('send_at', aliasedName, false,
type: i1.DriftSqlType.dateTime,
defaultValue: const CustomExpression(
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
i1.GeneratedColumn<DateTime> _column_30(String aliasedName) =>
i1.GeneratedColumn<DateTime>('updated_at', aliasedName, false,
type: i1.DriftSqlType.dateTime,
defaultValue: const CustomExpression(
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
class Shape2 extends i0.VersionedTable {
Shape2({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get deviceId =>
columnsByName['device_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i2.Uint8List> get identityKey =>
columnsByName['identity_key']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<int> _column_31(String aliasedName) =>
i1.GeneratedColumn<int>('device_id', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<String> _column_32(String aliasedName) =>
i1.GeneratedColumn<String>('name', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<i2.Uint8List> _column_33(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>('identity_key', aliasedName, false,
type: i1.DriftSqlType.blob);
class Shape3 extends i0.VersionedTable {
Shape3({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get preKeyId =>
columnsByName['pre_key_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<i2.Uint8List> get preKey =>
columnsByName['pre_key']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<int> _column_34(String aliasedName) =>
i1.GeneratedColumn<int>('pre_key_id', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<i2.Uint8List> _column_35(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>('pre_key', aliasedName, false,
type: i1.DriftSqlType.blob);
class Shape4 extends i0.VersionedTable {
Shape4({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get senderKeyName =>
columnsByName['sender_key_name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i2.Uint8List> get senderKey =>
columnsByName['sender_key']! as i1.GeneratedColumn<i2.Uint8List>;
}
i1.GeneratedColumn<String> _column_36(String aliasedName) =>
i1.GeneratedColumn<String>('sender_key_name', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<i2.Uint8List> _column_37(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>('sender_key', aliasedName, false,
type: i1.DriftSqlType.blob);
class Shape5 extends i0.VersionedTable {
Shape5({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get deviceId =>
columnsByName['device_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i2.Uint8List> get sessionRecord =>
columnsByName['session_record']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<i2.Uint8List> _column_38(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>('session_record', aliasedName, false,
type: i1.DriftSqlType.blob);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
case 1:
final schema = Schema2(database: database);
final migrator = i1.Migrator(database, schema);
await from1To2(migrator, schema);
return 2;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
};
}
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
));

View file

@ -1,16 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
enum MessageKind {
textMessage,
storedMediaFile,
media,
contactRequest,
profileChange,
rejectRequest,
acceptRequest,
opened,
ack
}
Map<String, Color> messageKindColors = { Map<String, Color> messageKindColors = {
"video": Colors.deepPurple, "video": Colors.deepPurple,

View file

@ -49,6 +49,7 @@
"messageSendState_Sending": "Wird gesendet", "messageSendState_Sending": "Wird gesendet",
"messageSendState_TapToLoad": "Tippe zum Laden", "messageSendState_TapToLoad": "Tippe zum Laden",
"messageSendState_Loading": "Herunterladen", "messageSendState_Loading": "Herunterladen",
"messageStoredInGalery": "In Gallerie gespeichert",
"imageEditorDrawOk": "Zeichnung machen", "imageEditorDrawOk": "Zeichnung machen",
"settingsTitle": "Einstellungen", "settingsTitle": "Einstellungen",
"settingsProfile": "Profil", "settingsProfile": "Profil",

View file

@ -50,6 +50,7 @@
"messageSendState_Sending": "Sending", "messageSendState_Sending": "Sending",
"messageSendState_TapToLoad": "Tap to load", "messageSendState_TapToLoad": "Tap to load",
"messageSendState_Loading": "Downloading", "messageSendState_Loading": "Downloading",
"messageStoredInGalery": "Stored in gallery",
"imageEditorDrawOk": "Take drawing", "imageEditorDrawOk": "Take drawing",
"settingsTitle": "Settings", "settingsTitle": "Settings",
"settingsProfile": "Profile", "settingsProfile": "Profile",

View file

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -15,32 +16,75 @@ import 'package:twonly/src/utils/signal.dart' as SignalHelper;
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
Future tryTransmitMessages() async { Future tryTransmitMessages() async {
List<Message> retransmit = Map<String, dynamic> retransmit = await getAllMessagesForRetransmitting();
await twonlyDatabase.messagesDao.getAllMessagesForRetransmitting();
if (retransmit.isEmpty) return; if (retransmit.isEmpty) return;
Logger("api.dart").info("try sending messages: ${retransmit.length}"); Logger("api.dart").info("try sending messages: ${retransmit.length}");
Box box = await getMediaStorage(); Map<String, dynamic> failed = {};
for (int i = 0; i < retransmit.length; i++) {
int msgId = retransmit[i].messageId; for (String key in retransmit.keys) {
RetransmitMessage msg =
RetransmitMessage.fromJson(jsonDecode(retransmit[key]));
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
if (bytes != null) {
Result resp = await apiProvider.sendTextMessage( Result resp = await apiProvider.sendTextMessage(
retransmit[i].contactId, msg.userId,
bytes, msg.bytes,
); );
if (resp.isSuccess) { if (resp.isSuccess) {
if (msg.messageId != null) {
await twonlyDatabase.messagesDao.updateMessageByMessageId( await twonlyDatabase.messagesDao.updateMessageByMessageId(
msgId, MessagesCompanion(acknowledgeByServer: Value(true))); msg.messageId!,
box.delete("retransmit-$msgId-textmessage"); MessagesCompanion(
acknowledgeByServer: Value(true),
),
);
}
failed[key] = retransmit[key];
} else { } else {
// in case of error do nothing. As the message is not removed the app will try again when relaunched // in case of error do nothing. As the message is not removed the app will try again when relaunched
} }
} }
Box box = await getMediaStorage();
box.put("messages-to-retransmit", jsonEncode(failed));
} }
class RetransmitMessage {
int? messageId;
int userId;
Uint8List bytes;
RetransmitMessage(
{this.messageId, required this.userId, required this.bytes});
// From JSON constructor
factory RetransmitMessage.fromJson(Map<String, dynamic> json) {
return RetransmitMessage(
messageId: json['messageId'],
userId: json['userId'],
bytes: base64Decode(json['bytes']),
);
}
// To JSON method
Map<String, dynamic> toJson() {
return {
'messageId': messageId,
'userId': userId,
'bytes': base64Encode(bytes),
};
}
}
Future<Map<String, dynamic>> getAllMessagesForRetransmitting() async {
Box box = await getMediaStorage();
String? retransmitJson = box.get("messages-to-retransmit");
Map<String, dynamic> retransmit = {};
if (retransmitJson != null) {
retransmit = jsonDecode(retransmitJson);
}
return retransmit;
} }
// this functions ensures that the message is received by the server and in case of errors will try again later // this functions ensures that the message is received by the server and in case of errors will try again later
@ -53,9 +97,19 @@ Future<Result> encryptAndSendMessage(
return Result.error(ErrorCode.InternalError); return Result.error(ErrorCode.InternalError);
} }
String stateId = (messageId ?? (60001 + Random().nextInt(100000))).toString();
Box box = await getMediaStorage(); Box box = await getMediaStorage();
if (messageId != null) {
box.put("retransmit-$messageId-textmessage", bytes); {
var retransmit = await getAllMessagesForRetransmitting();
retransmit[stateId] = jsonEncode(RetransmitMessage(
messageId: messageId,
userId: userId,
bytes: bytes,
).toJson());
box.put("messages-to-retransmit", jsonEncode(retransmit));
} }
Result resp = await apiProvider.sendTextMessage(userId, bytes); Result resp = await apiProvider.sendTextMessage(userId, bytes);
@ -66,6 +120,13 @@ Future<Result> encryptAndSendMessage(
messageId, messageId,
MessagesCompanion(acknowledgeByServer: Value(true)), MessagesCompanion(acknowledgeByServer: Value(true)),
); );
{
var retransmit = await getAllMessagesForRetransmitting();
retransmit.remove(stateId);
box.put("messages-to-retransmit", jsonEncode(retransmit));
}
box.delete("retransmit-$messageId-textmessage"); box.delete("retransmit-$messageId-textmessage");
} }
} }

View file

@ -341,7 +341,7 @@ Future sendImage(
Map<String, dynamic> allMediaFiles = {}; Map<String, dynamic> allMediaFiles = {};
if (mediaFilesJson != null) { if (mediaFilesJson != null) {
// allMediaFiles = jsonDecode(mediaFilesJson); allMediaFiles = jsonDecode(mediaFilesJson);
} }
allMediaFiles[stateId] = jsonEncode(states.toJson()); allMediaFiles[stateId] = jsonEncode(states.toJson());
@ -361,26 +361,42 @@ Future retransmitMediaFiles() async {
Map<String, dynamic> allMediaFiles = jsonDecode(mediaFilesJson); Map<String, dynamic> allMediaFiles = jsonDecode(mediaFilesJson);
bool allSuccess = true;
for (final entry in allMediaFiles.entries) { for (final entry in allMediaFiles.entries) {
try { try {
String stateId = entry.key; String stateId = entry.key;
States states = States.fromJson(jsonDecode(entry.value)); States states = States.fromJson(jsonDecode(entry.value));
// upload one by one // upload one by one
allSuccess = allSuccess &
await uploadMediaState(stateId, states.prepareState, states.metadata); await uploadMediaState(stateId, states.prepareState, states.metadata);
} catch (e) { } catch (e) {
Logger("media.dart").shout(e); Logger("media.dart").shout(e);
} }
} }
if (allSuccess) {
// when all retransmittions where sucessfull tag the errors
final pendings = await twonlyDatabase.messagesDao
.getAllMessagesPendingUploadOlderThanAMinute();
for (final pending in pendings) {
twonlyDatabase.messagesDao.updateMessageByMessageId(
pending.messageId,
MessagesCompanion(errorWhileSending: Value(true)),
);
}
}
// return allSuccess;
} }
// if the upload failes this function is called again from the retransmitMediaFiles function which is // if the upload failes this function is called again from the retransmitMediaFiles function which is
// called when the WebSocket is reconnected again. // called when the WebSocket is reconnected again.
Future uploadMediaState( Future<bool> uploadMediaState(
String stateId, PrepareState prepareState, Metadata metadata) async { String stateId, PrepareState prepareState, Metadata metadata) async {
final uploadState = final uploadState =
await ImageUploader.uploadState(prepareState, metadata.userIds.length); await ImageUploader.uploadState(prepareState, metadata.userIds.length);
if (uploadState == null) { if (uploadState == null) {
return; return false;
} }
{ {
@ -395,11 +411,8 @@ Future uploadMediaState(
} }
} }
final notifyState =
await ImageUploader.notifyState(prepareState, uploadState, metadata); await ImageUploader.notifyState(prepareState, uploadState, metadata);
if (notifyState == null) { return true;
return;
}
} }
Future tryDownloadMedia( Future tryDownloadMedia(

View file

@ -1,6 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
Future initMediaStorage() async { Future initMediaStorage() async {
@ -19,8 +21,9 @@ Future initMediaStorage() async {
} }
Future<Box> getMediaStorage() async { Future<Box> getMediaStorage() async {
final storage = getSecureStorage(); try {
await initMediaStorage(); await initMediaStorage();
final storage = getSecureStorage();
var encryptionKey = var encryptionKey =
base64Url.decode((await storage.read(key: 'hive_encryption_key'))!); base64Url.decode((await storage.read(key: 'hive_encryption_key'))!);
@ -29,4 +32,10 @@ Future<Box> getMediaStorage() async {
'media_storage', 'media_storage',
encryptionCipher: HiveAesCipher(encryptionKey), encryptionCipher: HiveAesCipher(encryptionKey),
); );
} catch (e) {
await customLocalPushNotification("Secure Storage Error",
"Settings > Apps > twonly > Storage and Cache > Press clear on both");
Logger("hive.dart").shout(e);
throw Exception(e);
}
} }

View file

@ -4,7 +4,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/json_models/message.dart' as my; import 'package:twonly/src/json_models/message.dart' as my;
@ -151,16 +152,16 @@ Future localPushNotificationNewMessage(
} }
} }
if (message.kind == my.MessageKind.contactRequest) { if (message.kind == MessageKind.contactRequest) {
msg = msg =
getPushNotificationText("contactRequest", getContactDisplayName(user)); getPushNotificationText("contactRequest", getContactDisplayName(user));
} }
if (message.kind == my.MessageKind.acceptRequest) { if (message.kind == MessageKind.acceptRequest) {
msg = getPushNotificationText("acceptRequest", getContactDisplayName(user)); msg = getPushNotificationText("acceptRequest", getContactDisplayName(user));
} }
if (message.kind == my.MessageKind.storedMediaFile) { if (message.kind == MessageKind.storedMediaFile) {
msg = msg =
getPushNotificationText("storedMediaFile", getContactDisplayName(user)); getPushNotificationText("storedMediaFile", getContactDisplayName(user));
} }
@ -193,3 +194,26 @@ Future localPushNotificationNewMessage(
payload: message.kind.index.toString(), payload: message.kind.index.toString(),
); );
} }
Future customLocalPushNotification(String title, String msg) async {
const AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'1',
'Error',
channelDescription: 'Error messages.',
importance: Importance.max,
priority: Priority.max,
);
const DarwinNotificationDetails darwinNotificationDetails =
DarwinNotificationDetails();
const NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails, iOS: darwinNotificationDetails);
await flutterLocalNotificationsPlugin.show(
897898,
title,
msg,
notificationDetails,
);
}

View file

@ -5,7 +5,7 @@ import 'package:twonly/globals.dart';
import 'package:twonly/src/components/image_editor/action_button.dart'; import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/media_view_sizing.dart'; import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/components/notification_badge.dart'; import 'package:twonly/src/components/notification_badge.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';

View file

@ -10,7 +10,7 @@ import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/headline.dart'; import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/send_next_media_to.dart'; import 'package:twonly/src/providers/send_next_media_to.dart';

View file

@ -10,7 +10,7 @@ import 'package:twonly/src/components/better_text.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart'; import 'package:twonly/src/components/message_send_state_icon.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.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';
import 'package:twonly/src/json_models/message.dart'; import 'package:twonly/src/json_models/message.dart';

View file

@ -9,7 +9,7 @@ import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart'; import 'package:twonly/src/components/message_send_state_icon.dart';
import 'package:twonly/src/components/notification_badge.dart'; import 'package:twonly/src/components/notification_badge.dart';
import 'package:twonly/src/components/user_context_menu.dart'; import 'package:twonly/src/components/user_context_menu.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.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';
import 'package:twonly/src/json_models/message.dart'; import 'package:twonly/src/json_models/message.dart';

View file

@ -2,7 +2,8 @@ import 'dart:async';
import 'package:drift/drift.dart' hide Column; import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/components/alert_dialog.dart'; import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';

View file

@ -6,7 +6,7 @@ import 'package:qr_flutter/qr_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/format_long_string.dart'; import 'package:twonly/src/components/format_long_string.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/signal.dart'; import 'package:twonly/src/utils/signal.dart';

View file

@ -7,10 +7,10 @@ import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/verified_shield.dart'; import 'package:twonly/src/components/verified_shield.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/contact/contact_verify_view.dart'; import 'package:twonly/src/views/contact/contact_verify_view.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
class ContactView extends StatefulWidget { class ContactView extends StatefulWidget {
const ContactView(this.userId, {super.key}); const ContactView(this.userId, {super.key});

View file

@ -2,7 +2,7 @@ import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';

View file

@ -0,0 +1,23 @@
// dart format width=80
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
import 'package:drift/drift.dart';
import 'package:drift/internal/migrations.dart';
import 'schema_v1.dart' as v1;
import 'schema_v2.dart' as v2;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
switch (version) {
case 1:
return v1.DatabaseAtV1(db);
case 2:
return v2.DatabaseAtV2(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [1, 2];
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
// dart format width=80
// ignore_for_file: unused_local_variable, unused_import
import 'package:drift/drift.dart';
import 'package:drift_dev/api/migrations_native.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:flutter_test/flutter_test.dart';
import 'generated/schema.dart';
import 'generated/schema_v1.dart' as v1;
import 'generated/schema_v2.dart' as v2;
void main() {
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
late SchemaVerifier verifier;
setUpAll(() {
verifier = SchemaVerifier(GeneratedHelper());
});
group('simple database migrations', () {
// These simple tests verify all possible schema updates with a simple (no
// data) migration. This is a quick way to ensure that written database
// migrations properly alter the schema.
const versions = GeneratedHelper.versions;
for (final (i, fromVersion) in versions.indexed) {
group('from $fromVersion', () {
for (final toVersion in versions.skip(i + 1)) {
test('to $toVersion', () async {
final schema = await verifier.schemaAt(fromVersion);
final db = TwonlyDatabase(schema.newConnection());
await verifier.migrateAndValidate(db, toVersion);
await db.close();
});
}
});
}
});
// The following template shows how to write tests ensuring your migrations
// preserve existing data.
// Testing this can be useful for migrations that change existing columns
// (e.g. by alterating their type or constraints). Migrations that only add
// tables or columns typically don't need these advanced tests. For more
// information, see https://drift.simonbinder.eu/migrations/tests/#verifying-data-integrity
// TODO: This generated template shows how these tests could be written. Adopt
// it to your own needs when testing migrations with data integrity.
test('migration from v1 to v2 does not corrupt data', () async {
// Add data to insert into the old database, and the expected rows after the
// migration.
// TODO: Fill these lists
final oldContactsData = <v1.ContactsData>[];
final expectedNewContactsData = <v2.ContactsData>[];
final oldMessagesData = <v1.MessagesData>[];
final expectedNewMessagesData = <v2.MessagesData>[];
final oldSignalIdentityKeyStoresData = <v1.SignalIdentityKeyStoresData>[];
final expectedNewSignalIdentityKeyStoresData =
<v2.SignalIdentityKeyStoresData>[];
final oldSignalPreKeyStoresData = <v1.SignalPreKeyStoresData>[];
final expectedNewSignalPreKeyStoresData = <v2.SignalPreKeyStoresData>[];
final oldSignalSenderKeyStoresData = <v1.SignalSenderKeyStoresData>[];
final expectedNewSignalSenderKeyStoresData =
<v2.SignalSenderKeyStoresData>[];
final oldSignalSessionStoresData = <v1.SignalSessionStoresData>[];
final expectedNewSignalSessionStoresData = <v2.SignalSessionStoresData>[];
await verifier.testWithDataIntegrity(
oldVersion: 1,
newVersion: 2,
createOld: v1.DatabaseAtV1.new,
createNew: v2.DatabaseAtV2.new,
openTestedDatabase: TwonlyDatabase.new,
createItems: (batch, oldDb) {
batch.insertAll(oldDb.contacts, oldContactsData);
batch.insertAll(oldDb.messages, oldMessagesData);
batch.insertAll(
oldDb.signalIdentityKeyStores, oldSignalIdentityKeyStoresData);
batch.insertAll(oldDb.signalPreKeyStores, oldSignalPreKeyStoresData);
batch.insertAll(
oldDb.signalSenderKeyStores, oldSignalSenderKeyStoresData);
batch.insertAll(oldDb.signalSessionStores, oldSignalSessionStoresData);
},
validateItems: (newDb) async {
expect(
expectedNewContactsData, await newDb.select(newDb.contacts).get());
expect(
expectedNewMessagesData, await newDb.select(newDb.messages).get());
expect(expectedNewSignalIdentityKeyStoresData,
await newDb.select(newDb.signalIdentityKeyStores).get());
expect(expectedNewSignalPreKeyStoresData,
await newDb.select(newDb.signalPreKeyStores).get());
expect(expectedNewSignalSenderKeyStoresData,
await newDb.select(newDb.signalSenderKeyStores).get());
expect(expectedNewSignalSessionStoresData,
await newDb.select(newDb.signalSessionStores).get());
},
);
});
}