diff --git a/lib/globals.dart b/lib/globals.dart index 0d801e1..72ab099 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -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; diff --git a/lib/main.dart b/lib/main.dart index 1a03b08..f859b2b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -41,16 +41,13 @@ void main() async { await dbProvider.ready; apiProvider = ApiProvider(); + twonlyDatabase = TwonlyDatabase(); FlutterForegroundTask.initCommunicationPort(); runApp( MultiProvider( providers: [ - Provider( - create: (context) => TwonlyDatabase(), - dispose: (context, db) => db.close(), - ), ChangeNotifierProvider(create: (_) => SendNextMediaTo()), ChangeNotifierProvider(create: (_) => settingsController), ], diff --git a/lib/src/components/best_friends_selector.dart b/lib/src/components/best_friends_selector.dart index bc35b77..0a31588 100644 --- a/lib/src/components/best_friends_selector.dart +++ b/lib/src/components/best_friends_selector.dart @@ -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(); diff --git a/lib/src/database/database.dart b/lib/src/database/database.dart index 0dc3656..1141c45 100644 --- a/lib/src/database/database.dart +++ b/lib/src/database/database.dart @@ -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> 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 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 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 getContactByUserId(int userId) { return select(contacts)..where((t) => t.userId.equals(userId)); } diff --git a/lib/src/database/messages_db.dart b/lib/src/database/messages_db.dart index e1a0700..34fca09 100644 --- a/lib/src/database/messages_db.dart +++ b/lib/src/database/messages_db.dart @@ -18,7 +18,8 @@ class Messages extends Table { IntColumn get responseToOtherMessageId => integer().nullable()(); BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))(); - IntColumn get downloadState => intEnum()(); + IntColumn get downloadState => intEnum() + .withDefault(Constant(DownloadState.pending.index))(); BoolColumn get acknowledgeByServer => boolean().withDefault(Constant(false))(); diff --git a/lib/src/providers/api/api.dart b/lib/src/providers/api/api.dart index 7ad566d..9133d0a 100644 --- a/lib/src/providers/api/api.dart +++ b/lib/src/providers/api/api.dart @@ -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 retransmit = - await DbMessages.getAllMessagesForRetransmitting(); + List 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 encryptAndSendMessage(int userId, Message msg) async { +Future encryptAndSendMessage(int userId, MessageJson msg) async { Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId); if (bytes == null) { @@ -71,7 +76,12 @@ Future 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 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 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"); diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index 2411c96..05fc458 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -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 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 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 handleDownloadData(DownloadData data) async { return client.Response()..ok = ok; } -Future handleNewMessage( - Int64 fromUserId, Uint8List body) async { - Message? message = await SignalHelper.getDecryptedText(fromUserId, body); +Future 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 handleNewMessage( encryptAndSendMessage( fromUserId, - Message( + MessageJson( kind: MessageKind.ack, messageId: message.messageId!, content: MessageContent(), @@ -184,19 +206,25 @@ Future 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 downloadToken = content.downloadToken; - tryDownloadMedia(messageId, fromUserId.toInt(), downloadToken); + tryDownloadMedia(messageId, fromUserId, downloadToken); } } } - localPushNotificationNewMessage( - fromUserId.toInt(), message, messageId); + localPushNotificationNewMessage(fromUserId, message, messageId); } } } diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index 94b4c3a..7753b47 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -39,7 +39,7 @@ class ApiProvider { // reconnection params Timer? reconnectionTimer; - int _reconnectionDelay = 5; + // int _reconnectionDelay = 5; final HashMap 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); diff --git a/lib/src/services/fcm_service.dart b/lib/src/services/fcm_service.dart index 7e189cf..013031a 100644 --- a/lib/src/services/fcm_service.dart +++ b/lib/src/services/fcm_service.dart @@ -65,8 +65,6 @@ Future initFCMService() async { }); } -late TwonlyDatabase bgTwonlyDB; - @pragma('vm:entry-point') Future _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 _firebaseMessagingBackgroundHandler(RemoteMessage message) async { Logger("firebase-background") .shout('Handling a background message: ${message.messageId}'); - bgTwonlyDB = TwonlyDatabase(); + twonlyDatabase = TwonlyDatabase(); apiProvider = ApiProvider(); await apiProvider.connect(); diff --git a/lib/src/services/notification_service.dart b/lib/src/services/notification_service.dart index cfcb7c9..b876891 100644 --- a/lib/src/services/notification_service.dart +++ b/lib/src/services/notification_service.dart @@ -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(), diff --git a/lib/src/utils/signal.dart b/lib/src/utils/signal.dart index 0a13cbf..d63ebc0 100644 --- a/lib/src/utils/signal.dart +++ b/lib/src/utils/signal.dart @@ -195,7 +195,7 @@ List? removeLastFourBytes(Uint8List original) { return [newList, lastFourBytes]; } -Future encryptBytes(Uint8List bytes, Int64 target) async { +Future encryptBytes(Uint8List bytes, int target) async { try { ConnectSignalProtocolStore signalStore = (await getSignalStore())!; @@ -248,7 +248,7 @@ Future decryptBytes(Uint8List bytes, int target) async { } } -Future encryptMessage(Message msg, int target) async { +Future encryptMessage(MessageJson msg, int target) async { try { ConnectSignalProtocolStore signalStore = (await getSignalStore())!; @@ -269,7 +269,7 @@ Future encryptMessage(Message msg, int target) async { } } -Future getDecryptedText(Int64 source, Uint8List msg) async { +Future getDecryptedText(int source, Uint8List msg) async { try { ConnectSignalProtocolStore signalStore = (await getSignalStore())!; @@ -293,8 +293,8 @@ Future 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()); diff --git a/lib/src/views/camera_to_share/share_image_editor_view.dart b/lib/src/views/camera_to_share/share_image_editor_view.dart index 43a338c..12d3885 100644 --- a/lib/src/views/camera_to_share/share_image_editor_view.dart +++ b/lib/src/views/camera_to_share/share_image_editor_view.dart @@ -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 { 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); } diff --git a/lib/src/views/camera_to_share/share_image_view.dart b/lib/src/views/camera_to_share/share_image_view.dart index a5bd786..3591ed9 100644 --- a/lib/src/views/camera_to_share/share_image_view.dart +++ b/lib/src/views/camera_to_share/share_image_view.dart @@ -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 { - List _users = []; + List contacts = []; List _otherUsers = []; List _bestFriends = []; int maxTotalMediaCounter = 0; Uint8List? imageBytes; bool sendingImage = false; - final HashSet _selectedUserIds = HashSet(); + final HashSet _selectedUserIds = HashSet(); final TextEditingController searchUserName = TextEditingController(); bool showRealTwonlyWarning = false; + late StreamSubscription> contactSub; @override void initState() { super.initState(); - _loadAsync(); - } - Future _loadAsync() async { int? sendNextMediaToUserId = context.read().sendNextMediaToUserId; if (sendNextMediaToUserId != null) { - _selectedUserIds.add(Int64(sendNextMediaToUserId)); + _selectedUserIds.add(sendNextMediaToUserId); } - _users = await DbContacts.getActiveUsers(); - _updateUsers(_users); - imageBytes = await widget.imageBytesFuture; - setState(() {}); + + Stream> allContacts = + twonlyDatabase.watchContactsForChatList(); + + contactSub = allContacts.listen((allContacts) { + setState(() { + contacts = allContacts; + }); + updateUsers(allContacts); + }); + + //_users = await DbContacts.getActiveUsers(); + // _updateUsers(_users); + // imageBytes = await widget.imageBytesFuture; + // setState(() {}); } - Future _updateUsers(List users) async { - Map flameCounters = - context.read().flamesCounter; + @override + void dispose() { + super.dispose(); + contactSub.cancel(); + } + Future updateUsers(List 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 { List 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 { Future _filterUsers(String query) async { if (query.isEmpty) { - _updateUsers(_users); + updateUsers(contacts); return; } - List usersFiltered = _users - .where((user) => - user.displayName.toLowerCase().contains(query.toLowerCase())) + List 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 users; final int maxTotalMediaCounter; final bool isRealTwonly; - final HashSet selectedUserIds; + final HashSet selectedUserIds; @override Widget build(BuildContext context) { // Step 1: Sort the users alphabetically - users.sort((a, b) => a.displayName.compareTo(b.displayName)); - - Map flameCounters = - context.watch().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( diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index b3c136b..276460c 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -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 { } Future initStreams() async { - Stream contact = context.db.watchContact(widget.userid); + Stream contact = twonlyDatabase.watchContact(widget.userid); userSub = contact.listen((contact) { setState(() { user = contact; @@ -172,7 +173,7 @@ class _ChatItemDetailsViewState extends State { }); Stream> 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 { } } 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(() { diff --git a/lib/src/views/chats/chat_list_view.dart b/lib/src/views/chats/chat_list_view.dart index f2ce88c..0964453 100644 --- a/lib/src/views/chats/chat_list_view.dart +++ b/lib/src/views/chats/chat_list_view.dart @@ -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 { @override Widget build(BuildContext context) { - Stream> contacts = context.db.watchContactsForChatList(); + Stream> contacts = twonlyDatabase.watchContactsForChatList(); return Scaffold( appBar: AppBar( @@ -50,7 +51,7 @@ class _ChatListViewState extends State { // 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 { @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(); diff --git a/lib/src/views/chats/media_viewer_view.dart b/lib/src/views/chats/media_viewer_view.dart index b58b15c..b8ea1f3 100644 --- a/lib/src/views/chats/media_viewer_view.dart +++ b/lib/src/views/chats/media_viewer_view.dart @@ -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 { Future asyncLoadNextMedia() async { Stream> messages = - context.db.watchMessageNotOpened(widget.userId); + twonlyDatabase.watchMessageNotOpened(widget.userId); _subscription = messages.listen((messages) { for (Message msg in messages) { diff --git a/lib/src/views/chats/search_username_view.dart b/lib/src/views/chats/search_username_view.dart index b48bc07..1e61bae 100644 --- a/lib/src/views/chats/search_username_view.dart +++ b/lib/src/views/chats/search_username_view.dart @@ -42,7 +42,7 @@ class _SearchUsernameView extends State { 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 { ); } - Stream> contacts = context.db.watchNotAcceptedContacts(); + Stream> contacts = twonlyDatabase.watchNotAcceptedContacts(); return Scaffold( appBar: AppBar( @@ -189,7 +189,8 @@ class _ContactsListViewState extends State { 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 { 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 { 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( diff --git a/lib/src/views/contact/contact_verify_view.dart b/lib/src/views/contact/contact_verify_view.dart index 582fa7e..75808ab 100644 --- a/lib/src/views/contact/contact_verify_view.dart +++ b/lib/src/views/contact/contact_verify_view.dart @@ -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 { @override Widget build(BuildContext context) { - Stream contact = context.db + Stream contact = twonlyDatabase .getContactByUserId(widget.contact.userId) .watchSingleOrNull(); @@ -144,7 +145,7 @@ class _ContactVerifyViewState extends State { 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 { 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), ); diff --git a/lib/src/views/contact/contact_view.dart b/lib/src/views/contact/contact_view.dart index 187e0aa..e8b3c46 100644 --- a/lib/src/views/contact/contact_view.dart +++ b/lib/src/views/contact/contact_view.dart @@ -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 { @override Widget build(BuildContext context) { Stream contact = - context.db.getContactByUserId(widget.userId).watchSingleOrNull(); + twonlyDatabase.getContactByUserId(widget.userId).watchSingleOrNull(); return Scaffold( appBar: AppBar( @@ -76,7 +77,7 @@ class _ContactViewState extends State { 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 { 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); diff --git a/lib/src/views/settings/privacy_view.dart b/lib/src/views/settings/privacy_view.dart index 55b0cc9..84287b6 100644 --- a/lib/src/views/settings/privacy_view.dart +++ b/lib/src/views/settings/privacy_view.dart @@ -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 { 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( diff --git a/lib/src/views/settings/privacy_view_block_users.dart b/lib/src/views/settings/privacy_view_block_users.dart index 0ae9818..b1bfcbf 100644 --- a/lib/src/views/settings/privacy_view_block_users.dart +++ b/lib/src/views/settings/privacy_view_block_users.dart @@ -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 { @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); } }