drift ready for contacts and messages

This commit is contained in:
otsmr 2025-03-09 21:46:06 +01:00
parent cfd6bd92cb
commit 5866d3e7e4
21 changed files with 314 additions and 173 deletions

View file

@ -1,5 +1,9 @@
import 'package:twonly/src/database/database.dart';
import 'package:twonly/src/providers/api_provider.dart';
import 'package:twonly/src/providers/db_provider.dart';
late ApiProvider apiProvider;
// uses for background notification
late DbProvider dbProvider;
late TwonlyDatabase twonlyDatabase;

View file

@ -41,16 +41,13 @@ void main() async {
await dbProvider.ready;
apiProvider = ApiProvider();
twonlyDatabase = TwonlyDatabase();
FlutterForegroundTask.initCommunicationPort();
runApp(
MultiProvider(
providers: [
Provider<TwonlyDatabase>(
create: (context) => TwonlyDatabase(),
dispose: (context, db) => db.close(),
),
ChangeNotifierProvider(create: (_) => SendNextMediaTo()),
ChangeNotifierProvider(create: (_) => settingsController),
],

View file

@ -1,5 +1,6 @@
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/database/contacts_db.dart';
import 'package:twonly/src/database/database.dart';
@ -141,7 +142,7 @@ class UserCheckbox extends StatelessWidget {
],
),
StreamBuilder(
stream: context.db.watchFlameCounter(user.userId),
stream: twonlyDatabase.watchFlameCounter(user.userId),
builder: (context, snapshot) {
if (!snapshot.hasData && snapshot.data! != 0) {
return Container();

View file

@ -1,5 +1,6 @@
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/database/contacts_db.dart';
import 'package:twonly/src/database/messages_db.dart';
@ -45,6 +46,11 @@ class TwonlyDatabase extends _$TwonlyDatabase {
.watch();
}
Future<List<Message>> getAllMessagesForRetransmitting() {
return (select(messages)..where((t) => t.acknowledgeByServer.equals(false)))
.get();
}
Future openedAllTextMessages(int contactId) {
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
return (update(messages)
@ -55,12 +61,61 @@ class TwonlyDatabase extends _$TwonlyDatabase {
.write(updates);
}
Future updateMessageByOtherUser(
int userId, int messageId, MessagesCompanion updatedValues) {
return (update(messages)
..where((c) =>
c.contactId.equals(userId) & c.messageId.equals(messageId)))
.write(updatedValues);
}
Future updateMessageByOtherMessageId(
int userId, int messageOtherId, MessagesCompanion updatedValues) {
return (update(messages)
..where((c) =>
c.contactId.equals(userId) &
c.messageOtherId.equals(messageOtherId)))
.write(updatedValues);
}
Future updateMessageByMessageId(
int messageId, MessagesCompanion updatedValues) {
return (update(messages)..where((c) => c.messageId.equals(messageId)))
.write(updatedValues);
}
Future<int?> insertMessage(MessagesCompanion message) async {
try {
return await into(messages).insert(message);
} catch (e) {
Logger("twonlyDatabase").shout("Error while inserting message: $e");
return null;
}
}
Future deleteMessageById(int messageId) {
return (delete(messages)..where((t) => t.messageId.equals(messageId))).go();
}
// ------------
Future<int> insertContact(ContactsCompanion contact) {
return into(contacts).insert(contact);
}
Future incTotalMediaCounter(int contactId) async {
return (update(contacts)..where((t) => t.userId.equals(contactId)))
.write(ContactsCompanion(
totalMediaCounter: Value(
(await (select(contacts)..where((t) => t.userId.equals(contactId)))
.get())
.first
.totalMediaCounter +
1,
),
));
}
SingleOrNullSelectable<Contact> getContactByUserId(int userId) {
return select(contacts)..where((t) => t.userId.equals(userId));
}

View file

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

View file

@ -1,15 +1,14 @@
import 'dart:convert';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart';
import 'package:drift/drift.dart';
import 'package:hive/hive.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart';
import '../../../../.blocked/archives/contacts_model.dart';
import 'package:twonly/src/database/database.dart';
import 'package:twonly/src/database/messages_db.dart';
import 'package:twonly/src/model/json/message.dart';
import '../../../../.blocked/archives/messages_model.dart';
import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/utils/misc.dart';
@ -17,8 +16,10 @@ import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
Future tryTransmitMessages() async {
List<DbMessage> retransmit =
await DbMessages.getAllMessagesForRetransmitting();
List<Message> retransmit =
await twonlyDatabase.getAllMessagesForRetransmitting();
if (retransmit.isEmpty) return;
Logger("api.dart").info("try sending messages: ${retransmit.length}");
@ -30,12 +31,16 @@ Future tryTransmitMessages() async {
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
if (bytes != null) {
Result resp = await apiProvider.sendTextMessage(
retransmit[i].otherUserId,
retransmit[i].contactId,
bytes,
);
if (resp.isSuccess) {
DbMessages.acknowledgeMessageByServer(msgId);
await twonlyDatabase.updateMessageByMessageId(
msgId,
MessagesCompanion(acknowledgeByServer: Value(true))
);
box.delete("retransmit-$msgId-textmessage");
} else {
// in case of error do nothing. As the message is not removed the app will try again when relaunched
@ -44,9 +49,9 @@ Future tryTransmitMessages() async {
Uint8List? encryptedMedia = await box.get("retransmit-$msgId-media");
if (encryptedMedia != null) {
final content = retransmit[i].messageContent;
final content = MessageJson.fromJson(jsonDecode(retransmit[i].contentJson!)).content;
if (content is MediaMessageContent) {
uploadMediaFile(msgId, retransmit[i].otherUserId, encryptedMedia,
uploadMediaFile(msgId, retransmit[i].contactId, encryptedMedia,
content.isRealTwonly, content.maxShowTime, retransmit[i].sendAt);
}
}
@ -54,7 +59,7 @@ Future tryTransmitMessages() async {
}
// this functions ensures that the message is received by the server and in case of errors will try again later
Future<Result> encryptAndSendMessage(int userId, Message msg) async {
Future<Result> encryptAndSendMessage(int userId, MessageJson msg) async {
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
if (bytes == null) {
@ -71,7 +76,12 @@ Future<Result> encryptAndSendMessage(int userId, Message msg) async {
if (resp.isSuccess) {
if (msg.messageId != null) {
DbMessages.acknowledgeMessageByServer(msg.messageId!);
await twonlyDatabase.updateMessageByMessageId(
msg.messageId!,
MessagesCompanion(acknowledgeByServer: Value(true))
);
box.delete("retransmit-${msg.messageId}-textmessage");
}
}
@ -84,15 +94,18 @@ Future sendTextMessage(int target, String message) async {
DateTime messageSendAt = DateTime.now();
int? messageId = await DbMessages.insertMyMessage(
target.toInt(),
MessageKind.textMessage,
content,
messageSendAt,
);
int? messageId = await twonlyDatabase.insertMessage(MessagesCompanion(
contactId: Value(target),
kind: Value(MessageKind.textMessage),
sendAt: Value(messageSendAt),
downloadState: Value(DownloadState.downloaded),
contentJson: Value(jsonEncode(content.toJson()))
),);
if (messageId == null) return;
Message msg = Message(
MessageJson msg = MessageJson(
kind: MessageKind.textMessage,
messageId: messageId,
content: content,
@ -160,13 +173,19 @@ Future uploadMediaFile(
box.delete("retransmit-$messageId-media");
box.delete("retransmit-$messageId-uploadtoken");
await DbContacts.updateTotalMediaCounter(target.toInt());
twonlyDatabase.incTotalMediaCounter(target);
twonlyDatabase.updateContact(
target,
ContactsCompanion(
lastMessageReceived: Value(messageSendAt),
),
);
// Ensures the retransmit of the message
await encryptAndSendMessage(
target,
Message(
kind: MessageKind.image,
MessageJson(
kind: MessageKind.media,
messageId: messageId,
content: MediaMessageContent(
downloadToken: uploadToken,
@ -210,17 +229,19 @@ class SendImage {
}
messageSendAt = DateTime.now();
messageId = await DbMessages.insertMyMessage(
userId.toInt(),
MessageKind.image,
MediaMessageContent(
int? messageId = await twonlyDatabase.insertMessage(MessagesCompanion(
contactId: Value(userId),
kind: Value(MessageKind.media),
sendAt: Value(messageSendAt!),
downloadState: Value(DownloadState.pending),
contentJson: Value(jsonEncode(MediaMessageContent(
downloadToken: [],
maxShowTime: maxShowTime,
isRealTwonly: isRealTwonly,
isVideo: false,
),
messageSendAt!,
);
).toJson()))
));
// should only happen when there is no space left on the smartphone -> abort message
if (messageId == null) return;
@ -295,9 +316,15 @@ Future tryDownloadMedia(int messageId, int fromUserId, List<int> mediaToken,
if (media != null && media.isNotEmpty) {
offset = media.length;
}
globalCallBackOnDownloadChange(mediaToken, true);
box.put("${mediaToken}_messageId", messageId);
box.put("${mediaToken}_fromUserId", fromUserId);
final update =
MessagesCompanion(downloadState: Value(DownloadState.downloading));
await twonlyDatabase.updateMessageByOtherUser(
fromUserId,
messageId,
update
);
apiProvider.triggerDownload(mediaToken, offset);
}
@ -326,7 +353,12 @@ Future<Uint8List?> getDownloadedMedia(
}
if (media == null) return null;
await userOpenedOtherMessage(otherUserId, messageOtherId);
// await userOpenedOtherMessage(otherUserId, messageOtherId);
notifyContactAboutOpeningMessage(otherUserId, messageOtherId);
twonlyDatabase.updateMessageByOtherMessageId(otherUserId, messageOtherId, MessagesCompanion(
openedAt: Value(DateTime.now())
));
box.delete(mediaToken.toString());
box.put("${mediaToken}_downloaded", "deleted");
box.delete("${mediaToken}_messageId");

View file

@ -1,13 +1,14 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:drift/drift.dart';
import 'package:fixnum/fixnum.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:logging/logging.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart';
import '../../../../.blocked/archives/contacts_model.dart';
import 'package:twonly/src/database/database.dart';
import 'package:twonly/src/database/messages_db.dart';
import 'package:twonly/src/model/json/message.dart';
import '../../../../.blocked/archives/messages_model.dart';
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
import 'package:twonly/src/proto/api/error.pb.dart';
@ -27,7 +28,7 @@ Future handleServerMessage(server.ServerToClient msg) async {
response = await handleRequestNewPreKey();
} else if (msg.v0.hasNewMessage()) {
Uint8List body = Uint8List.fromList(msg.v0.newMessage.body);
Int64 fromUserId = msg.v0.newMessage.fromUserId;
int fromUserId = msg.v0.newMessage.fromUserId.toInt();
response = await handleNewMessage(fromUserId, body);
} else if (msg.v0.hasDownloaddata()) {
response = await handleDownloadData(msg.v0.downloaddata);
@ -62,18 +63,13 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
// media file was deleted by the server. remove the media from device
if (messageId != null) {
int? fromUserId = await DbMessages.deleteMessageById(messageId);
await twonlyDatabase.deleteMessageById(messageId);
box.delete(boxId);
if (fromUserId != null) {
globalCallBackOnMessageChange(fromUserId, messageId);
}
box.delete("${data.uploadToken}_fromUserId");
box.delete("${data.uploadToken}_downloaded");
globalCallBackOnDownloadChange(data.uploadToken, false);
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
} else {
globalCallBackOnDownloadChange(data.uploadToken, false);
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}
@ -111,9 +107,15 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
.shout("error decrypting the message: ${data.uploadToken}");
}
final update =
MessagesCompanion(downloadState: Value(DownloadState.downloaded));
await twonlyDatabase.updateMessageByOtherUser(
fromUserId,
messageId!,
update,
);
box.delete(boxId);
await globalCallBackOnMessageChange(fromUserId, messageId);
globalCallBackOnDownloadChange(data.uploadToken, false);
}
} else {
box.put(boxId, downloadedBytes);
@ -123,52 +125,72 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
return client.Response()..ok = ok;
}
Future<client.Response> handleNewMessage(
Int64 fromUserId, Uint8List body) async {
Message? message = await SignalHelper.getDecryptedText(fromUserId, body);
Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
MessageJson? message = await SignalHelper.getDecryptedText(fromUserId, body);
if (message != null) {
switch (message.kind) {
case MessageKind.contactRequest:
Result username = await apiProvider.getUsername(fromUserId);
if (username.isSuccess) {
Uint8List name = username.value.userdata.username;
DbContacts.insertNewContact(
utf8.decode(name), fromUserId.toInt(), true);
localPushNotificationNewMessage(fromUserId.toInt(), message, 999999);
int added = await twonlyDatabase.insertContact(ContactsCompanion(
username: Value(utf8.decode(name)),
userId: Value(fromUserId),
requested: Value(true),
));
if (added > 0) {
localPushNotificationNewMessage(
fromUserId.toInt(),
message,
999999,
);
}
}
break;
case MessageKind.opened:
await DbMessages.otherUserOpenedMyMessage(
fromUserId.toInt(),
final update = MessagesCompanion(openedAt: Value(message.timestamp));
await twonlyDatabase.updateMessageByOtherUser(
fromUserId,
message.messageId!,
message.timestamp,
update,
);
break;
case MessageKind.rejectRequest:
DbContacts.deleteUser(fromUserId.toInt());
await twonlyDatabase.deleteContactByUserId(fromUserId);
break;
case MessageKind.acceptRequest:
DbContacts.acceptUser(fromUserId.toInt());
final update = ContactsCompanion(accepted: Value(true));
twonlyDatabase.updateContact(fromUserId, update);
localPushNotificationNewMessage(fromUserId.toInt(), message, 8888888);
break;
case MessageKind.ack:
DbMessages.acknowledgeMessageByUser(
fromUserId.toInt(), message.messageId!);
final update = MessagesCompanion(acknowledgeByUser: Value(true));
await twonlyDatabase.updateMessageByOtherUser(
fromUserId,
message.messageId!,
update,
);
break;
default:
if (message.kind != MessageKind.textMessage &&
message.kind != MessageKind.video &&
message.kind != MessageKind.image) {
message.kind != MessageKind.media) {
Logger("handleServerMessages")
.shout("Got unknown MessageKind $message");
} else {
String content = jsonEncode(message.content.toJson());
int? messageId = await DbMessages.insertOtherMessage(
fromUserId.toInt(),
message.kind,
message.messageId!,
content,
message.timestamp);
String content = jsonEncode(message.content!.toJson());
final update = MessagesCompanion(
contactId: Value(fromUserId),
kind: Value(message.kind),
messageOtherId: Value(message.messageId),
contentJson: Value(content),
sendAt: Value(message.timestamp),
);
final messageId = await twonlyDatabase.insertMessage(
update,
);
if (messageId == null) {
return client.Response()..error = ErrorCode.InternalError;
@ -176,7 +198,7 @@ Future<client.Response> handleNewMessage(
encryptAndSendMessage(
fromUserId,
Message(
MessageJson(
kind: MessageKind.ack,
messageId: message.messageId!,
content: MessageContent(),
@ -184,19 +206,25 @@ Future<client.Response> handleNewMessage(
),
);
if (message.kind == MessageKind.video ||
message.kind == MessageKind.image) {
await DbContacts.updateTotalMediaCounter(fromUserId.toInt());
if (message.kind == MessageKind.media) {
twonlyDatabase.updateContact(
fromUserId,
ContactsCompanion(
lastMessageReceived: Value(message.timestamp),
),
);
twonlyDatabase.incTotalMediaCounter(fromUserId);
if (!globalIsAppInBackground) {
final content = message.content;
if (content is MediaMessageContent) {
List<int> downloadToken = content.downloadToken;
tryDownloadMedia(messageId, fromUserId.toInt(), downloadToken);
tryDownloadMedia(messageId, fromUserId, downloadToken);
}
}
}
localPushNotificationNewMessage(
fromUserId.toInt(), message, messageId);
localPushNotificationNewMessage(fromUserId, message, messageId);
}
}
}

View file

@ -39,7 +39,7 @@ class ApiProvider {
// reconnection params
Timer? reconnectionTimer;
int _reconnectionDelay = 5;
// int _reconnectionDelay = 5;
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
IOWebSocketChannel? _channel;
@ -68,7 +68,7 @@ class ApiProvider {
Future onConnected() async {
await authenticate();
globalCallbackConnectionState(true);
_reconnectionDelay = 5;
// _reconnectionDelay = 5;
if (!globalIsAppInBackground) {
tryTransmitMessages();
@ -131,24 +131,24 @@ class ApiProvider {
void tryToReconnect() {
return;
if (globalIsAppInBackground) return;
if (reconnectionTimer != null) {
reconnectionTimer!.cancel();
}
// if (globalIsAppInBackground) return;
// if (reconnectionTimer != null) {
// reconnectionTimer!.cancel();
// }
final int randomDelay = Random().nextInt(20);
final int delay = _reconnectionDelay + randomDelay;
// final int randomDelay = Random().nextInt(20);
// final int delay = _reconnectionDelay + randomDelay;
debugPrint("Delay reconnection $delay");
// debugPrint("Delay reconnection $delay");
reconnectionTimer = Timer(Duration(seconds: delay), () async {
// increase delay but set a maximum of 60 seconds (including the random delay)
_reconnectionDelay = _reconnectionDelay * 2;
if (_reconnectionDelay > 40) {
_reconnectionDelay = 40;
}
await connect();
});
// reconnectionTimer = Timer(Duration(seconds: delay), () async {
// // increase delay but set a maximum of 60 seconds (including the random delay)
// _reconnectionDelay = _reconnectionDelay * 2;
// if (_reconnectionDelay > 40) {
// _reconnectionDelay = 40;
// }
// await connect();
// });
}
void _onData(dynamic msgBuffer) {
@ -289,7 +289,7 @@ class ApiProvider {
..username = username
..publicIdentityKey =
(await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
..registrationId = signalIdentity.registrationId
..registrationId = Int64(signalIdentity.registrationId)
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
..signedPrekeySignature = signedPreKey.signature
..signedPrekeyId = Int64(signedPreKey.id);

View file

@ -65,8 +65,6 @@ Future initFCMService() async {
});
}
late TwonlyDatabase bgTwonlyDB;
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Wenn Tasks länger als 30 Sekunden ausgeführt werden, wird der Prozess möglicherweise automatisch vom Gerät beendet.
@ -75,7 +73,7 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
Logger("firebase-background")
.shout('Handling a background message: ${message.messageId}');
bgTwonlyDB = TwonlyDatabase();
twonlyDatabase = TwonlyDatabase();
apiProvider = ApiProvider();
await apiProvider.connect();

View file

@ -3,6 +3,8 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:logging/logging.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/contacts_db.dart';
import 'package:twonly/src/database/database.dart';
import 'package:twonly/src/model/json/message.dart' as my;
@ -164,10 +166,9 @@ String getPushNotificationText(String key, String userName) {
}
Future localPushNotificationNewMessage(
int fromUserId, my.Message message, int messageId) async {
Contact? user = await TwonlyDatabase.provider
.getContactByUserId(fromUserId)
.getSingleOrNull();
int fromUserId, my.MessageJson message, int messageId) async {
Contact? user =
await twonlyDatabase.getContactByUserId(fromUserId).getSingleOrNull();
if (user == null) return;
@ -176,23 +177,25 @@ Future localPushNotificationNewMessage(
final content = message.content;
if (content is my.TextMessageContent) {
msg = getPushNotificationText("newTextMessage", user.displayName);
msg =
getPushNotificationText("newTextMessage", getContactDisplayName(user));
} else if (content is my.MediaMessageContent) {
if (content.isRealTwonly) {
msg = getPushNotificationText("newTwonly", user.displayName);
msg = getPushNotificationText("newTwonly", getContactDisplayName(user));
} else if (content.isVideo) {
msg = getPushNotificationText("newVideo", user.displayName);
msg = getPushNotificationText("newVideo", getContactDisplayName(user));
} else {
msg = getPushNotificationText("newImage", user.displayName);
msg = getPushNotificationText("newImage", getContactDisplayName(user));
}
}
if (message.kind == my.MessageKind.contactRequest) {
msg = getPushNotificationText("contactRequest", user.displayName);
msg =
getPushNotificationText("contactRequest", getContactDisplayName(user));
}
if (message.kind == my.MessageKind.acceptRequest) {
msg = getPushNotificationText("acceptRequest", user.displayName);
msg = getPushNotificationText("acceptRequest", getContactDisplayName(user));
}
if (msg == "") {
@ -213,7 +216,7 @@ Future localPushNotificationNewMessage(
NotificationDetails(android: androidNotificationDetails);
await flutterLocalNotificationsPlugin.show(
messageId,
user.displayName,
getContactDisplayName(user),
msg,
notificationDetails,
payload: message.kind.index.toString(),

View file

@ -195,7 +195,7 @@ List<Uint8List>? removeLastFourBytes(Uint8List original) {
return [newList, lastFourBytes];
}
Future<Uint8List?> encryptBytes(Uint8List bytes, Int64 target) async {
Future<Uint8List?> encryptBytes(Uint8List bytes, int target) async {
try {
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
@ -248,7 +248,7 @@ Future<Uint8List?> decryptBytes(Uint8List bytes, int target) async {
}
}
Future<Uint8List?> encryptMessage(Message msg, int target) async {
Future<Uint8List?> encryptMessage(MessageJson msg, int target) async {
try {
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
@ -269,7 +269,7 @@ Future<Uint8List?> encryptMessage(Message msg, int target) async {
}
}
Future<Message?> getDecryptedText(Int64 source, Uint8List msg) async {
Future<MessageJson?> getDecryptedText(int source, Uint8List msg) async {
try {
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
@ -293,8 +293,8 @@ Future<Message?> getDecryptedText(Int64 source, Uint8List msg) async {
} else {
return null;
}
Message dectext =
Message.fromJson(jsonDecode(utf8.decode(gzip.decode(plaintext))));
MessageJson dectext =
MessageJson.fromJson(jsonDecode(utf8.decode(gzip.decode(plaintext))));
return dectext;
} catch (e) {
Logger("utils/signal").shout(e.toString());

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.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/notification_badge.dart';
@ -55,7 +56,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Future updateAsync(int userId) async {
if (sendNextMediaToUserName != null) return;
Contact? contact =
await context.db.getContactByUserId(userId).getSingleOrNull();
await twonlyDatabase.getContactByUserId(userId).getSingleOrNull();
if (contact != null) {
sendNextMediaToUserName = getContactDisplayName(contact);
}

View file

@ -1,17 +1,18 @@
import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/best_friends_selector.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/verified_shield.dart';
import '../../../../.blocked/archives/contacts_model.dart';
import 'package:twonly/src/database/contacts_db.dart';
import 'package:twonly/src/database/database.dart';
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/messages_change_provider.dart';
import 'package:twonly/src/providers/send_next_media_to.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/home_view.dart';
@ -31,43 +32,55 @@ class ShareImageView extends StatefulWidget {
}
class _ShareImageView extends State<ShareImageView> {
List<Contact> _users = [];
List<Contact> contacts = [];
List<Contact> _otherUsers = [];
List<Contact> _bestFriends = [];
int maxTotalMediaCounter = 0;
Uint8List? imageBytes;
bool sendingImage = false;
final HashSet<Int64> _selectedUserIds = HashSet<Int64>();
final HashSet<int> _selectedUserIds = HashSet<int>();
final TextEditingController searchUserName = TextEditingController();
bool showRealTwonlyWarning = false;
late StreamSubscription<List<Contact>> contactSub;
@override
void initState() {
super.initState();
_loadAsync();
}
Future<void> _loadAsync() async {
int? sendNextMediaToUserId =
context.read<SendNextMediaTo>().sendNextMediaToUserId;
if (sendNextMediaToUserId != null) {
_selectedUserIds.add(Int64(sendNextMediaToUserId));
}
_users = await DbContacts.getActiveUsers();
_updateUsers(_users);
imageBytes = await widget.imageBytesFuture;
setState(() {});
_selectedUserIds.add(sendNextMediaToUserId);
}
Future _updateUsers(List<Contact> users) async {
Map<int, int> flameCounters =
context.read<MessagesChangeProvider>().flamesCounter;
Stream<List<Contact>> allContacts =
twonlyDatabase.watchContactsForChatList();
contactSub = allContacts.listen((allContacts) {
setState(() {
contacts = allContacts;
});
updateUsers(allContacts);
});
//_users = await DbContacts.getActiveUsers();
// _updateUsers(_users);
// imageBytes = await widget.imageBytesFuture;
// setState(() {});
}
@override
void dispose() {
super.dispose();
contactSub.cancel();
}
Future updateUsers(List<Contact> users) async {
// Sort contacts by flameCounter and then by totalMediaCounter
users.sort((a, b) {
// First, compare by flameCounter
int flameComparison = (flameCounters[b.userId.toInt()] ?? 0)
.compareTo((flameCounters[a.userId.toInt()] ?? 0));
int flameComparison = (getFlameCounterFromContact(b))
.compareTo((getFlameCounterFromContact(a)));
if (flameComparison != 0) {
return flameComparison; // Sort by flameCounter in descending order
}
@ -87,8 +100,7 @@ class _ShareImageView extends State<ShareImageView> {
List<Contact> otherUsers = [];
for (var contact in users) {
if ((flameCounters[contact.userId.toInt()] ?? 0) > 0 &&
bestFriends.length < 6) {
if ((getFlameCounterFromContact(contact)) > 0 && bestFriends.length < 6) {
bestFriends.add(contact);
} else {
otherUsers.add(contact);
@ -103,19 +115,20 @@ class _ShareImageView extends State<ShareImageView> {
Future _filterUsers(String query) async {
if (query.isEmpty) {
_updateUsers(_users);
updateUsers(contacts);
return;
}
List<Contact> usersFiltered = _users
.where((user) =>
user.displayName.toLowerCase().contains(query.toLowerCase()))
List<Contact> usersFiltered = contacts
.where((user) => getContactDisplayName(user)
.toLowerCase()
.contains(query.toLowerCase()))
.toList();
_updateUsers(usersFiltered);
updateUsers(usersFiltered);
}
void updateStatus(Int64 userId, bool checked) {
void updateStatus(int userId, bool checked) {
if (widget.isRealTwonly) {
Contact user = _users.firstWhere((x) => x.userId == userId);
Contact user = contacts.firstWhere((x) => x.userId == userId);
if (!user.verified) {
showRealTwonlyWarning = true;
setState(() {});
@ -248,26 +261,24 @@ class UserList extends StatelessWidget {
required this.updateStatus,
required this.isRealTwonly,
});
final Function(Int64, bool) updateStatus;
final Function(int, bool) updateStatus;
final List<Contact> users;
final int maxTotalMediaCounter;
final bool isRealTwonly;
final HashSet<Int64> selectedUserIds;
final HashSet<int> selectedUserIds;
@override
Widget build(BuildContext context) {
// Step 1: Sort the users alphabetically
users.sort((a, b) => a.displayName.compareTo(b.displayName));
Map<int, int> flameCounters =
context.watch<MessagesChangeProvider>().flamesCounter;
users.sort(
(a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)));
return ListView.builder(
restorationId: 'new_message_users_list',
itemCount: users.length,
itemBuilder: (BuildContext context, int i) {
Contact user = users[i];
int flameCounter = flameCounters[user.userId.toInt()] ?? 0;
int flameCounter = getFlameCounterFromContact(user);
return ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.start, // Center horizontally
@ -278,7 +289,7 @@ class UserList extends StatelessWidget {
padding: const EdgeInsets.only(right: 1),
child: VerifiedShield(user),
),
Text(user.displayName),
Text(getContactDisplayName(user)),
if (flameCounter >= 1)
FlameCounterWidget(
user,
@ -289,7 +300,7 @@ class UserList extends StatelessWidget {
],
),
leading: InitialsAvatar(
displayName: user.displayName,
getContactDisplayName(user),
fontSize: 15,
),
trailing: Checkbox(

View file

@ -4,6 +4,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart';
@ -164,7 +165,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
}
Future initStreams() async {
Stream<Contact> contact = context.db.watchContact(widget.userid);
Stream<Contact> contact = twonlyDatabase.watchContact(widget.userid);
userSub = contact.listen((contact) {
setState(() {
user = contact;
@ -172,7 +173,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
});
Stream<List<Message>> msgStream =
context.db.watchAllMessagesFrom(widget.userid);
twonlyDatabase.watchAllMessagesFrom(widget.userid);
messageSub = msgStream.listen((msgs) {
if (!context.mounted) return;
var updated = false;
@ -186,7 +187,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
}
}
if (updated) {
context.db.openedAllTextMessages(widget.userid);
twonlyDatabase.openedAllTextMessages(widget.userid);
} else {
// The stream should be get an update, so only update the UI when all are opened
setState(() {

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart';
@ -32,7 +33,7 @@ class ChatListView extends StatefulWidget {
class _ChatListViewState extends State<ChatListView> {
@override
Widget build(BuildContext context) {
Stream<List<Contact>> contacts = context.db.watchContactsForChatList();
Stream<List<Contact>> contacts = twonlyDatabase.watchContactsForChatList();
return Scaffold(
appBar: AppBar(
@ -50,7 +51,7 @@ class _ChatListViewState extends State<ChatListView> {
// title:
actions: [
StreamBuilder(
stream: context.db.watchContactsRequested(),
stream: twonlyDatabase.watchContactsRequested(),
builder: (context, snapshot) {
var count = 0;
if (snapshot.hasData && snapshot.data != null) {
@ -193,8 +194,8 @@ class _UserListItem extends State<UserListItem> {
@override
Widget build(BuildContext context) {
final notOpenedMessages =
context.db.watchMessageNotOpened(widget.user.userId);
final lastMessage = context.db.watchLastMessage(widget.user.userId);
twonlyDatabase.watchMessageNotOpened(widget.user.userId);
final lastMessage = twonlyDatabase.watchLastMessage(widget.user.userId);
// if (widget.lastMessage != null) {
// state = widget.lastMessage!.getSendState();

View file

@ -6,6 +6,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:lottie/lottie.dart';
import 'package:no_screenshot/no_screenshot.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/database/database.dart';
@ -56,7 +57,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
Future asyncLoadNextMedia() async {
Stream<List<Message>> messages =
context.db.watchMessageNotOpened(widget.userId);
twonlyDatabase.watchMessageNotOpened(widget.userId);
_subscription = messages.listen((messages) {
for (Message msg in messages) {

View file

@ -42,7 +42,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
return;
}
int added = await context.db.insertContact(ContactsCompanion(
int added = await twonlyDatabase.insertContact(ContactsCompanion(
username: Value(searchUserName.text),
userId: Value(res.value.userdata.userId),
requested: Value(false),
@ -89,7 +89,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
);
}
Stream<List<Contact>> contacts = context.db.watchNotAcceptedContacts();
Stream<List<Contact>> contacts = twonlyDatabase.watchNotAcceptedContacts();
return Scaffold(
appBar: AppBar(
@ -189,7 +189,8 @@ class _ContactsListViewState extends State<ContactsListView> {
color: const Color.fromARGB(164, 244, 67, 54)),
onPressed: () async {
final update = ContactsCompanion(blocked: Value(true));
await context.db.updateContact(contact.userId, update);
await twonlyDatabase.updateContact(
contact.userId, update);
},
),
),
@ -198,7 +199,8 @@ class _ContactsListViewState extends State<ContactsListView> {
child: IconButton(
icon: Icon(Icons.close, color: Colors.red),
onPressed: () async {
await context.db.deleteContactByUserId(contact.userId);
await twonlyDatabase
.deleteContactByUserId(contact.userId);
encryptAndSendMessage(
contact.userId,
MessageJson(
@ -214,7 +216,7 @@ class _ContactsListViewState extends State<ContactsListView> {
icon: Icon(Icons.check, color: Colors.green),
onPressed: () async {
final update = ContactsCompanion(accepted: Value(true));
await context.db.updateContact(contact.userId, update);
await twonlyDatabase.updateContact(contact.userId, update);
encryptAndSendMessage(
contact.userId,
MessageJson(

View file

@ -3,6 +3,7 @@ import 'package:drift/drift.dart' hide Column;
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/format_long_string.dart';
import 'package:flutter/material.dart';
import 'package:twonly/src/database/contacts_db.dart';
@ -36,7 +37,7 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
@override
Widget build(BuildContext context) {
Stream<Contact?> contact = context.db
Stream<Contact?> contact = twonlyDatabase
.getContactByUserId(widget.contact.userId)
.watchSingleOrNull();
@ -144,7 +145,7 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
onPressed: () {
final update =
ContactsCompanion(verified: Value(false));
context.db.updateContact(contact.userId, update);
twonlyDatabase.updateContact(contact.userId, update);
},
label: Text(
context.lang.contactVerifyNumberClearVerification),
@ -154,7 +155,7 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
icon: FaIcon(FontAwesomeIcons.shieldHeart),
onPressed: () {
final update = ContactsCompanion(verified: Value(true));
context.db.updateContact(contact.userId, update);
twonlyDatabase.updateContact(contact.userId, update);
},
label: Text(context.lang.contactVerifyNumberMarkAsVerified),
);

View file

@ -1,5 +1,6 @@
import 'package:drift/drift.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/components/better_list_title.dart';
import 'package:twonly/src/components/flame.dart';
@ -24,7 +25,7 @@ class _ContactViewState extends State<ContactView> {
@override
Widget build(BuildContext context) {
Stream<Contact?> contact =
context.db.getContactByUserId(widget.userId).watchSingleOrNull();
twonlyDatabase.getContactByUserId(widget.userId).watchSingleOrNull();
return Scaffold(
appBar: AppBar(
@ -76,7 +77,7 @@ class _ContactViewState extends State<ContactView> {
if (context.mounted && nickName != null && nickName != "") {
final update = ContactsCompanion(nickName: Value(nickName));
context.db.updateContact(contact.userId, update);
twonlyDatabase.updateContact(contact.userId, update);
}
},
),
@ -106,7 +107,8 @@ class _ContactViewState extends State<ContactView> {
if (block) {
final update = ContactsCompanion(blocked: Value(true));
if (context.mounted) {
await context.db.updateContact(contact.userId, update);
await twonlyDatabase.updateContact(
contact.userId, update);
}
if (context.mounted) {
Navigator.popUntil(context, (route) => route.isFirst);

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/settings/privacy_view_block_users.dart';
@ -26,7 +27,7 @@ class _PrivacyViewState extends State<PrivacyView> {
ListTile(
title: Text(context.lang.settingsPrivacyBlockUsers),
subtitle: StreamBuilder(
stream: context.db.watchContactsBlocked(),
stream: twonlyDatabase.watchContactsBlocked(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return Text(

View file

@ -1,5 +1,6 @@
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/database/contacts_db.dart';
import 'package:twonly/src/database/database.dart';
@ -20,7 +21,7 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
@override
void initState() {
super.initState();
allUsers = context.db.watchAllContacts();
allUsers = twonlyDatabase.watchAllContacts();
loadAsync();
}
@ -104,7 +105,7 @@ class UserList extends StatelessWidget {
Future block(BuildContext context, int userId, bool? value) async {
if (value != null) {
final update = ContactsCompanion(blocked: Value(!value));
await context.db.updateContact(userId, update);
await twonlyDatabase.updateContact(userId, update);
}
}