From 4260c63ce26839bb38d533705fe26404c1985c5d Mon Sep 17 00:00:00 2001 From: otsmr Date: Sat, 25 Oct 2025 01:08:59 +0200 Subject: [PATCH] fixing all compile errors --- lib/app.dart | 11 - lib/globals.dart | 1 - lib/main.dart | 1 - lib/src/database/daos/contacts.dao.dart | 147 +- lib/src/database/daos/groups.dao.dart | 141 +- lib/src/database/daos/mediafiles.dao.dart | 5 + lib/src/database/daos/messages.dao.dart | 164 +- lib/src/database/daos/messages.dao.g.dart | 2 + lib/src/database/daos/reactions.dao.dart | 11 + lib/src/database/tables/contacts.table.dart | 16 - lib/src/database/tables/groups.table.dart | 20 +- lib/src/database/tables/messages.table.dart | 15 +- lib/src/database/twonly.db.dart | 2 +- lib/src/database/twonly.db.g.dart | 2538 ++++++++--------- lib/src/model/json/message_old.dart | 331 --- lib/src/model/json/userdata.dart | 2 +- lib/src/model/json/userdata.g.dart | 4 +- .../client/generated/messages.pb.dart | 64 +- .../client/generated/messages.pbjson.dart | 121 +- lib/src/model/protobuf/client/messages.proto | 7 +- .../api/mediafiles/download.service.dart | 5 +- .../api/mediafiles/upload.service.dart | 18 +- lib/src/services/api/messages.dart | 41 +- .../contact.server_messages.dart | 16 +- .../media.server_messages.dart | 16 +- .../messages.server_messages.dart | 3 +- lib/src/services/api/utils.dart | 2 +- lib/src/services/flame.service.dart | 51 +- .../mediafiles/mediafile.service.dart | 14 +- lib/src/utils/misc.dart | 29 +- .../best_friends_selector.dart | 89 +- .../views/camera/share_image_editor_view.dart | 6 +- lib/src/views/camera/share_image_view.dart | 207 +- lib/src/views/chats/add_new_user.view.dart | 43 +- lib/src/views/chats/chat_list.view.dart | 300 +- .../chat_list_components/group_list_item.dart | 227 ++ .../last_message_time.dart | 8 +- lib/src/views/chats/chat_messages.view.dart | 332 +-- .../chat_list_entry.dart | 73 +- .../chat_media_entry.dart | 100 +- .../chat_reaction_row.dart | 117 +- .../chat_text_entry.dart | 20 +- .../in_chat_media_viewer.dart | 13 +- .../message_context_menu.dart | 59 +- .../message_send_state_icon.dart | 230 +- .../response_container.dart | 102 +- lib/src/views/chats/media_viewer.view.dart | 529 +--- .../emoji_reactions_row.component.dart | 92 + .../reaction_buttons.component.dart | 112 + lib/src/views/chats/start_new_chat.view.dart | 49 +- .../components/avatar_icon.component.dart | 94 + lib/src/views/components/flame.dart | 61 +- lib/src/views/components/initialsavatar.dart | 64 - .../user_context_menu.component.dart | 8 +- lib/src/views/contact/contact.view.dart | 19 +- lib/src/views/groups/group.view.dart | 18 + .../memories/memories_photo_slider.view.dart | 6 +- .../developer/automated_testing.view.dart | 24 +- .../settings/developer/developer.view.dart | 19 +- .../settings/privacy_view_block.users.dart | 6 +- .../views/settings/settings_main.view.dart | 4 +- 61 files changed, 3223 insertions(+), 3606 deletions(-) delete mode 100644 lib/src/model/json/message_old.dart create mode 100644 lib/src/views/chats/chat_list_components/group_list_item.dart create mode 100644 lib/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart create mode 100644 lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart create mode 100644 lib/src/views/components/avatar_icon.component.dart delete mode 100644 lib/src/views/components/initialsavatar.dart create mode 100644 lib/src/views/groups/group.view.dart diff --git a/lib/app.dart b/lib/app.dart index 48cc497..bc9f76b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -43,18 +43,7 @@ class _AppState extends State with WidgetsBindingObserver { Future setUserPlan() async { final user = await getUser(); - globalBestFriendUserId = -1; if (user != null && mounted) { - if (user.myBestFriendContactId != null) { - final contact = await twonlyDB.contactsDao - .getContactByUserId(user.myBestFriendContactId!) - .getSingleOrNull(); - if (contact != null) { - if (contact.alsoBestFriend) { - globalBestFriendUserId = user.myBestFriendContactId ?? 0; - } - } - } if (mounted) { await context .read() diff --git a/lib/globals.dart b/lib/globals.dart index d08f1fa..36562dd 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -27,4 +27,3 @@ void Function() globalCallbackNewDeviceRegistered = () {}; void Function(String planId) globalCallbackUpdatePlan = (String planId) {}; bool globalIsAppInBackground = true; -int globalBestFriendUserId = -1; diff --git a/lib/main.dart b/lib/main.dart index eb2fdb4..19fee5c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,7 +47,6 @@ void main() async { await initFileDownloader(); // await twonlyDB.messagesDao.resetPendingDownloadState(); - // await twonlyDB.messagesDao.handleMediaFilesOlderThan30Days(); // await twonlyDB.messageRetransmissionDao.purgeOldRetransmissions(); // await twonlyDB.signalDao.purgeOutDatedPreKeys(); diff --git a/lib/src/database/daos/contacts.dao.dart b/lib/src/database/daos/contacts.dao.dart index 58e6b69..de674dc 100644 --- a/lib/src/database/daos/contacts.dao.dart +++ b/lib/src/database/daos/contacts.dao.dart @@ -20,79 +20,6 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { } } - Future incFlameCounter( - int contactId, - bool received, - DateTime timestamp, - ) async { - final contact = (await (select(contacts) - ..where((t) => t.userId.equals(contactId))) - .get()) - .first; - - final totalMediaCounter = contact.totalMediaCounter + 1; - var flameCounter = contact.flameCounter; - - if (contact.lastMessageReceived != null && - contact.lastMessageSend != null) { - final now = DateTime.now(); - final startOfToday = DateTime(now.year, now.month, now.day); - final twoDaysAgo = startOfToday.subtract(const Duration(days: 2)); - if (contact.lastMessageSend!.isBefore(twoDaysAgo) || - contact.lastMessageReceived!.isBefore(twoDaysAgo)) { - flameCounter = 0; - } - } - - var lastMessageSend = const Value.absent(); - var lastMessageReceived = const Value.absent(); - var lastFlameCounterChange = const Value.absent(); - - if (contact.lastFlameCounterChange != null) { - final now = DateTime.now(); - final startOfToday = DateTime(now.year, now.month, now.day); - - if (contact.lastFlameCounterChange!.isBefore(startOfToday)) { - // last flame update was yesterday. check if it can be updated. - var updateFlame = false; - if (received) { - if (contact.lastMessageSend != null && - contact.lastMessageSend!.isAfter(startOfToday)) { - // today a message was already send -> update flame - updateFlame = true; - } - } else if (contact.lastMessageReceived != null && - contact.lastMessageReceived!.isAfter(startOfToday)) { - // today a message was already received -> update flame - updateFlame = true; - } - if (updateFlame) { - flameCounter += 1; - lastFlameCounterChange = Value(timestamp); - } - } - } else { - // There where no message until no... - lastFlameCounterChange = Value(timestamp); - } - - if (received) { - lastMessageReceived = Value(timestamp); - } else { - lastMessageSend = Value(timestamp); - } - - return (update(contacts)..where((t) => t.userId.equals(contactId))).write( - ContactsCompanion( - totalMediaCounter: Value(totalMediaCounter), - lastFlameCounterChange: lastFlameCounterChange, - lastMessageReceived: lastMessageReceived, - lastMessageSend: lastMessageSend, - flameCounter: Value(flameCounter), - ), - ); - } - SingleOrNullSelectable getContactByUserId(int userId) { return select(contacts)..where((t) => t.userId.equals(userId)); } @@ -135,37 +62,6 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { .watchSingleOrNull(); } - // Stream> watchContactsForShareView() { - // return (select(contacts) - // ..where( - // (t) => - // t.accepted.equals(true) & - // t.blocked.equals(false) & - // t.deleted.equals(false), - // ) - // ..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)])) - // .watch(); - // } - - // Stream> watchContactsForStartNewChat() { - // return (select(contacts) - // ..where((t) => t.accepted.equals(true) & t.blocked.equals(false)) - // ..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)])) - // .watch(); - // } - - // Stream> watchContactsForChatList() { - // return (select(contacts) - // ..where( - // (t) => - // t.accepted.equals(true) & - // t.blocked.equals(false) & - // t.archived.equals(false), - // ) - // ..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)])) - // .watch(); - // } - Future> getAllNotBlockedContacts() { return (select(contacts)..where((t) => t.blocked.equals(false))).get(); } @@ -188,31 +84,15 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { return query.map((row) => row.read(count)).watchSingle(); } + Stream> watchAllAcceptedContacts() { + return (select(contacts) + ..where((t) => t.blocked.equals(false) & t.accepted.equals(true))) + .watch(); + } + Stream> watchAllContacts() { return select(contacts).watch(); } - - Future modifyFlameCounterForTesting() async { - await update(contacts).write( - ContactsCompanion( - lastFlameCounterChange: Value(DateTime.now()), - flameCounter: const Value(1337), - lastFlameSync: const Value(null), - ), - ); - } - - Stream watchFlameCounter(int userId) { - return (select(contacts) - ..where( - (u) => - u.userId.equals(userId) & - u.lastMessageReceived.isNotNull() & - u.lastMessageSend.isNotNull(), - )) - .watchSingle() - .asyncMap(getFlameCounterFromContact); - } } String getContactDisplayName(Contact user) { @@ -234,18 +114,3 @@ String getContactDisplayName(Contact user) { String applyStrikethrough(String text) { return text.split('').map((char) => '$char\u0336').join(); } - -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(const Duration(days: 2)); - if (contact.lastMessageSend!.isAfter(twoDaysAgo) && - contact.lastMessageReceived!.isAfter(twoDaysAgo)) { - return contact.flameCounter + 1; - } else { - return 0; - } -} diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index 6456434..cfd2157 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -32,7 +32,56 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { .get(); } - Future> getDirectChat(int userId) async { + Future insertGroup(GroupsCompanion group) async { + await into(groups).insert(group); + } + + Future> getGroupContact(String groupId) async { + final query = select(contacts).join([ + leftOuterJoin( + groupMembers, + groupMembers.contactId.equalsExp(contacts.userId) & + groupMembers.groupId.equals(groupId), + ), + ]); + return query.map((row) => row.readTable(contacts)).get(); + } + + Stream> watchGroups() { + return select(groups).watch(); + } + + Stream watchGroup(String groupId) { + return (select(groups)..where((t) => t.groupId.equals(groupId))) + .watchSingleOrNull(); + } + + Stream> watchGroupsForChatList() { + return (select(groups)..where((t) => t.archived.equals(false))).watch(); + } + + Future getGroup(String groupId) { + return (select(groups)..where((t) => t.groupId.equals(groupId))) + .getSingleOrNull(); + } + + Stream watchFlameCounter(String groupId) { + return (select(groups) + ..where( + (u) => + u.groupId.equals(groupId) & + u.lastMessageReceived.isNotNull() & + u.lastMessageSend.isNotNull(), + )) + .watchSingle() + .asyncMap(getFlameCounterFromGroup); + } + + Future> getAllDirectChats() { + return (select(groups)..where((t) => t.isDirectChat.equals(true))).get(); + } + + Future getDirectChat(int userId) async { final query = (select(groups).join([ leftOuterJoin( groupMembers, @@ -40,8 +89,94 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { groupMembers.contactId.equals(userId), ), ]) - ..where(groups.isGroupOfTwo.equals(true))); + ..where(groups.isDirectChat.equals(true))); - return query.map((row) => row.readTable(groups)).get(); + return query.map((row) => row.readTable(groups)).getSingleOrNull(); + } + + Future incFlameCounter( + String groupId, + bool received, + DateTime timestamp, + ) async { + final group = await (select(groups) + ..where((t) => t.groupId.equals(groupId))) + .getSingle(); + + final totalMediaCounter = group.totalMediaCounter + 1; + var flameCounter = group.flameCounter; + + if (group.lastMessageReceived != null && group.lastMessageSend != null) { + final now = DateTime.now(); + final startOfToday = DateTime(now.year, now.month, now.day); + final twoDaysAgo = startOfToday.subtract(const Duration(days: 2)); + if (group.lastMessageSend!.isBefore(twoDaysAgo) || + group.lastMessageReceived!.isBefore(twoDaysAgo)) { + flameCounter = 0; + } + } + + var lastMessageSend = const Value.absent(); + var lastMessageReceived = const Value.absent(); + var lastFlameCounterChange = const Value.absent(); + + if (group.lastFlameCounterChange != null) { + final now = DateTime.now(); + final startOfToday = DateTime(now.year, now.month, now.day); + + if (group.lastFlameCounterChange!.isBefore(startOfToday)) { + // last flame update was yesterday. check if it can be updated. + var updateFlame = false; + if (received) { + if (group.lastMessageSend != null && + group.lastMessageSend!.isAfter(startOfToday)) { + // today a message was already send -> update flame + updateFlame = true; + } + } else if (group.lastMessageReceived != null && + group.lastMessageReceived!.isAfter(startOfToday)) { + // today a message was already received -> update flame + updateFlame = true; + } + if (updateFlame) { + flameCounter += 1; + lastFlameCounterChange = Value(timestamp); + } + } + } else { + // There where no message until no... + lastFlameCounterChange = Value(timestamp); + } + + if (received) { + lastMessageReceived = Value(timestamp); + } else { + lastMessageSend = Value(timestamp); + } + + await (update(groups)..where((t) => t.groupId.equals(groupId))).write( + GroupsCompanion( + totalMediaCounter: Value(totalMediaCounter), + lastFlameCounterChange: lastFlameCounterChange, + lastMessageReceived: lastMessageReceived, + lastMessageSend: lastMessageSend, + flameCounter: Value(flameCounter), + ), + ); + } +} + +int getFlameCounterFromGroup(Group group) { + if (group.lastMessageSend == null || group.lastMessageReceived == null) { + return 0; + } + final now = DateTime.now(); + final startOfToday = DateTime(now.year, now.month, now.day); + final twoDaysAgo = startOfToday.subtract(const Duration(days: 2)); + if (group.lastMessageSend!.isAfter(twoDaysAgo) && + group.lastMessageReceived!.isAfter(twoDaysAgo)) { + return group.flameCounter + 1; + } else { + return 0; } } diff --git a/lib/src/database/daos/mediafiles.dao.dart b/lib/src/database/daos/mediafiles.dao.dart index 72d5062..872f73c 100644 --- a/lib/src/database/daos/mediafiles.dao.dart +++ b/lib/src/database/daos/mediafiles.dao.dart @@ -46,6 +46,11 @@ class MediaFilesDao extends DatabaseAccessor .getSingleOrNull(); } + Stream watchMedia(String mediaId) { + return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId))) + .watchSingleOrNull(); + } + Future resetPendingDownloadState() async { await (update(mediaFiles) ..where( diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index 7a0726f..9a7829a 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -4,6 +4,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; +import 'package:twonly/src/database/tables/reactions.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -15,7 +16,9 @@ part 'messages.dao.g.dart'; Messages, Contacts, MediaFiles, + Reactions, MessageHistories, + GroupMembers, MessageActions, Groups, ], @@ -26,55 +29,39 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { // ignore: matching_super_parameters MessagesDao(super.db); - // Stream> watchMessageNotOpened(int contactId) { - // return (select(messages) - // ..where( - // (t) => - // t.openedAt.isNull() & - // t.contactId.equals(contactId) & - // t.errorWhileSending.equals(false), - // ) - // ..orderBy([(t) => OrderingTerm.desc(t.sendAt)])) - // .watch(); - // } + Stream> watchMessageNotOpened(String groupId) { + return (select(messages) + ..where((t) => t.openedAt.isNull() & t.groupId.equals(groupId)) + ..orderBy([(t) => OrderingTerm.desc(t.createdAt)])) + .watch(); + } - // Stream> watchMediaMessageNotOpened(int contactId) { - // return (select(messages) - // ..where( - // (t) => - // t.openedAt.isNull() & - // t.contactId.equals(contactId) & - // t.errorWhileSending.equals(false) & - // t.messageOtherId.isNotNull() & - // t.kind.equals(MessageKind.media.name), - // ) - // ..orderBy([(t) => OrderingTerm.asc(t.sendAt)])) - // .watch(); - // } + Stream> watchMediaNotOpened(String groupId) { + return (select(messages) + ..where( + (t) => + t.openedAt.isNull() & + t.groupId.equals(groupId) & + t.senderId.isNotNull() & + t.type.equals(MessageType.media.name), + ) + ..orderBy([(t) => OrderingTerm.asc(t.createdAt)])) + .watch(); + } - // Stream> watchLastMessage(int contactId) { - // return (select(messages) - // ..where((t) => t.contactId.equals(contactId)) - // ..orderBy([(t) => OrderingTerm.desc(t.sendAt)]) - // ..limit(1)) - // .watch(); - // } + Stream> watchLastMessage(String groupId) { + return (select(messages) + ..where((t) => t.groupId.equals(groupId)) + ..orderBy([(t) => OrderingTerm.desc(t.createdAt)]) + ..limit(1)) + .watch(); + } - // Stream> watchAllMessagesFrom(int contactId) { - // return (select(messages) - // ..where( - // (t) => - // t.contactId.equals(contactId) & - // t.contentJson.isNotNull() & - // (t.openedAt.isNull() | - // t.mediaStored.equals(true) | - // t.openedAt.isBiggerThanValue( - // DateTime.now().subtract(const Duration(days: 1)), - // )), - // ) - // ..orderBy([(t) => OrderingTerm.asc(t.sendAt)])) - // .watch(); - // } + Stream> watchByGroupId(String groupId) { + return ((select(messages)..where((t) => t.groupId.equals(groupId))) + ..orderBy([(t) => OrderingTerm.asc(t.createdAt)])) + .watch(); + } // Future removeOldMessages() { // return (update(messages) @@ -92,22 +79,6 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { // .write(const MessagesCompanion(contentJson: Value(null))); // } - // Future handleMediaFilesOlderThan30Days() { - // /// media files will be deleted by the server after 30 days, so delete them here also - // return (update(messages) - // ..where( - // (t) => (t.kind.equals(MessageKind.media.name) & - // t.openedAt.isNull() & - // t.messageOtherId.isNull() & - // (t.sendAt.isSmallerThanValue( - // DateTime.now().subtract( - // const Duration(days: 30), - // ), - // ))), - // )) - // .write(const MessagesCompanion(errorWhileSending: Value(true))); - // } - // Future> getAllMessagesPendingDownloading() { // return (select(messages) // ..where( @@ -155,18 +126,18 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { // .get(); // } - // Future openedAllNonMediaMessages(int contactId) { - // final updates = MessagesCompanion(openedAt: Value(DateTime.now())); - // return (update(messages) - // ..where( - // (t) => - // t.contactId.equals(contactId) & - // t.messageOtherId.isNotNull() & - // t.openedAt.isNull() & - // t.kind.equals(MessageKind.media.name).not(), - // )) - // .write(updates); - // } + Future openedAllTextMessages(String groupId) { + final updates = MessagesCompanion(openedAt: Value(DateTime.now())); + return (update(messages) + ..where( + (t) => + t.groupId.equals(groupId) & + t.senderId.isNotNull() & + t.openedAt.isNull() & + t.type.equals(MessageType.text.name), + )) + .write(updates); + } Future handleMessageDeletion( int contactId, @@ -259,6 +230,22 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { ); } + Future haveAllMembers( + String groupId, + String messageId, + MessageActionType action, + ) async { + final members = await twonlyDB.groupsDao.getGroupMembers(groupId); + + final actions = await (select(messageActions) + ..where( + (t) => t.type.equals(action.name) & t.messageId.equals(messageId), + )) + .get(); + + return members.length == actions.length; + } + // Future updateMessageByOtherUser( // int userId, // int messageId, @@ -321,6 +308,29 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { } } + Future getLastMessageAction(String messageId) async { + return (((select(messageActions) + ..where( + (t) => t.messageId.equals(messageId), + )) + ..orderBy([(t) => OrderingTerm.desc(t.actionAt)])) + ..limit(1)) + .getSingleOrNull(); + } + + Future reopenedMedia(String messageId) async { + await (delete(messageActions) + ..where( + (t) => + t.messageId.equals(messageId) & + t.contactId.isNull() & + t.type.equals( + MessageActionType.openedAt.name, + ), + )) + .go(); + } + // Future deleteMessagesByContactId(int contactId) { // return (delete(messages) // ..where( @@ -342,9 +352,9 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { // .go(); // } - // Future deleteMessagesByMessageId(int messageId) { - // return (delete(messages)..where((t) => t.messageId.equals(messageId))).go(); - // } + Future deleteMessagesById(String messageId) { + return (delete(messages)..where((t) => t.messageId.equals(messageId))).go(); + } // Future deleteAllMessagesByContactId(int contactId) { // return (delete(messages)..where((t) => t.contactId.equals(contactId))).go(); diff --git a/lib/src/database/daos/messages.dao.g.dart b/lib/src/database/daos/messages.dao.g.dart index 74bd53f..feb9283 100644 --- a/lib/src/database/daos/messages.dao.g.dart +++ b/lib/src/database/daos/messages.dao.g.dart @@ -8,7 +8,9 @@ mixin _$MessagesDaoMixin on DatabaseAccessor { $ContactsTable get contacts => attachedDatabase.contacts; $MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles; $MessagesTable get messages => attachedDatabase.messages; + $ReactionsTable get reactions => attachedDatabase.reactions; $MessageHistoriesTable get messageHistories => attachedDatabase.messageHistories; + $GroupMembersTable get groupMembers => attachedDatabase.groupMembers; $MessageActionsTable get messageActions => attachedDatabase.messageActions; } diff --git a/lib/src/database/daos/reactions.dao.dart b/lib/src/database/daos/reactions.dao.dart index 1325d30..93f02a4 100644 --- a/lib/src/database/daos/reactions.dao.dart +++ b/lib/src/database/daos/reactions.dao.dart @@ -43,4 +43,15 @@ class ReactionsDao extends DatabaseAccessor with _$ReactionsDaoMixin { Log.error(e); } } + + Stream> watchReactions(String messageId) { + return (select(reactions) + ..where((t) => t.messageId.equals(messageId)) + ..orderBy([(t) => OrderingTerm.desc(t.createdAt)])) + .watch(); + } + + Future insertReaction(ReactionsCompanion reaction) async { + await into(reactions).insert(reaction); + } } diff --git a/lib/src/database/tables/contacts.table.dart b/lib/src/database/tables/contacts.table.dart index 0aa40c0..2fa8096 100644 --- a/lib/src/database/tables/contacts.table.dart +++ b/lib/src/database/tables/contacts.table.dart @@ -13,28 +13,12 @@ class Contacts extends Table { BoolColumn get accepted => boolean().withDefault(const Constant(false))(); BoolColumn get requested => boolean().withDefault(const Constant(false))(); - BoolColumn get hidden => boolean().withDefault(const Constant(false))(); BoolColumn get blocked => boolean().withDefault(const Constant(false))(); BoolColumn get verified => boolean().withDefault(const Constant(false))(); BoolColumn get deleted => boolean().withDefault(const Constant(false))(); - BoolColumn get alsoBestFriend => - boolean().withDefault(const Constant(false))(); - - IntColumn get deleteMessagesAfterXMinutes => - integer().withDefault(const Constant(60 * 24))(); - DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); - IntColumn get totalMediaCounter => integer().withDefault(const Constant(0))(); - - DateTimeColumn get lastMessageSend => dateTime().nullable()(); - DateTimeColumn get lastMessageReceived => dateTime().nullable()(); - DateTimeColumn get lastFlameCounterChange => dateTime().nullable()(); - DateTimeColumn get lastFlameSync => dateTime().nullable()(); - - IntColumn get flameCounter => integer().withDefault(const Constant(0))(); - @override Set get primaryKey => {userId}; } diff --git a/lib/src/database/tables/groups.table.dart b/lib/src/database/tables/groups.table.dart index 27b6438..674f2ac 100644 --- a/lib/src/database/tables/groups.table.dart +++ b/lib/src/database/tables/groups.table.dart @@ -7,15 +7,31 @@ class Groups extends Table { TextColumn get groupId => text().clientDefault(() => uuid.v4())(); BoolColumn get isGroupAdmin => boolean()(); - BoolColumn get isGroupOfTwo => boolean()(); + BoolColumn get isDirectChat => boolean()(); BoolColumn get pinned => boolean().withDefault(const Constant(false))(); BoolColumn get archived => boolean().withDefault(const Constant(false))(); TextColumn get groupName => text()(); + IntColumn get totalMediaCounter => integer().withDefault(const Constant(0))(); + + BoolColumn get alsoBestFriend => + boolean().withDefault(const Constant(false))(); + + IntColumn get deleteMessagesAfterMilliseconds => + integer().withDefault(const Constant(1000 * 60 * 24))(); + + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + + DateTimeColumn get lastMessageSend => dateTime().nullable()(); + DateTimeColumn get lastMessageReceived => dateTime().nullable()(); + DateTimeColumn get lastFlameCounterChange => dateTime().nullable()(); + DateTimeColumn get lastFlameSync => dateTime().nullable()(); + + IntColumn get flameCounter => integer().withDefault(const Constant(0))(); + DateTimeColumn get lastMessageExchange => dateTime().withDefault(currentDateAndTime)(); - DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); @override Set get primaryKey => {groupId}; diff --git a/lib/src/database/tables/messages.table.dart b/lib/src/database/tables/messages.table.dart index 11651c8..0e1c70d 100644 --- a/lib/src/database/tables/messages.table.dart +++ b/lib/src/database/tables/messages.table.dart @@ -33,9 +33,9 @@ class Messages extends Table { BoolColumn get isDeletedFromSender => boolean().withDefault(const Constant(false))(); - BoolColumn get isEdited => boolean().withDefault(const Constant(false))(); - + DateTimeColumn get openedAt => dateTime().nullable()(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get modifiedAt => dateTime().nullable()(); @override Set get primaryKey => {messageId}; @@ -43,15 +43,12 @@ class Messages extends Table { enum MessageActionType { openedAt, - modifiedAt, ackByUserAt, ackByServerAt, } @DataClassName('MessageAction') class MessageActions extends Table { - IntColumn get id => integer().autoIncrement()(); - TextColumn get messageId => text().references(Messages, #messageId, onDelete: KeyAction.cascade)(); @@ -59,10 +56,11 @@ class MessageActions extends Table { integer().references(Contacts, #contactId, onDelete: KeyAction.cascade)(); TextColumn get type => textEnum()(); + DateTimeColumn get actionAt => dateTime().withDefault(currentDateAndTime)(); @override - Set get primaryKey => {id}; + Set get primaryKey => {messageId, contactId, type}; } @DataClassName('MessageHistory') @@ -72,8 +70,9 @@ class MessageHistories extends Table { TextColumn get messageId => text().references(Messages, #messageId, onDelete: KeyAction.cascade)(); - IntColumn get contactId => - integer().references(Contacts, #contactId, onDelete: KeyAction.cascade)(); + IntColumn get contactId => integer() + .nullable() + .references(Contacts, #contactId, onDelete: KeyAction.cascade)(); TextColumn get content => text().nullable()(); diff --git a/lib/src/database/twonly.db.dart b/lib/src/database/twonly.db.dart index 1e121c1..22e3907 100644 --- a/lib/src/database/twonly.db.dart +++ b/lib/src/database/twonly.db.dart @@ -42,7 +42,7 @@ part 'twonly.db.g.dart'; SignalSessionStores, SignalContactPreKeys, SignalContactSignedPreKeys, - MessageActions + MessageActions, ], daos: [ MessagesDao, diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index a285163..de4a722 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -65,15 +65,6 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("requested" IN (0, 1))'), defaultValue: const Constant(false)); - static const VerificationMeta _hiddenMeta = const VerificationMeta('hidden'); - @override - late final GeneratedColumn hidden = GeneratedColumn( - 'hidden', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("hidden" IN (0, 1))'), - defaultValue: const Constant(false)); static const VerificationMeta _blockedMeta = const VerificationMeta('blocked'); @override @@ -104,25 +95,6 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("deleted" IN (0, 1))'), defaultValue: const Constant(false)); - static const VerificationMeta _alsoBestFriendMeta = - const VerificationMeta('alsoBestFriend'); - @override - late final GeneratedColumn alsoBestFriend = GeneratedColumn( - 'also_best_friend', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("also_best_friend" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _deleteMessagesAfterXMinutesMeta = - const VerificationMeta('deleteMessagesAfterXMinutes'); - @override - late final GeneratedColumn deleteMessagesAfterXMinutes = - GeneratedColumn( - 'delete_messages_after_x_minutes', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(60 * 24)); static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override @@ -131,46 +103,6 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { type: DriftSqlType.dateTime, requiredDuringInsert: false, defaultValue: currentDateAndTime); - static const VerificationMeta _totalMediaCounterMeta = - const VerificationMeta('totalMediaCounter'); - @override - late final GeneratedColumn totalMediaCounter = GeneratedColumn( - 'total_media_counter', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - static const VerificationMeta _lastMessageSendMeta = - const VerificationMeta('lastMessageSend'); - @override - late final GeneratedColumn lastMessageSend = - GeneratedColumn('last_message_send', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastMessageReceivedMeta = - const VerificationMeta('lastMessageReceived'); - @override - late final GeneratedColumn lastMessageReceived = - GeneratedColumn('last_message_received', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastFlameCounterChangeMeta = - const VerificationMeta('lastFlameCounterChange'); - @override - late final GeneratedColumn lastFlameCounterChange = - GeneratedColumn('last_flame_counter_change', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastFlameSyncMeta = - const VerificationMeta('lastFlameSync'); - @override - late final GeneratedColumn lastFlameSync = - GeneratedColumn('last_flame_sync', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _flameCounterMeta = - const VerificationMeta('flameCounter'); - @override - late final GeneratedColumn flameCounter = GeneratedColumn( - 'flame_counter', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); @override List get $columns => [ userId, @@ -181,19 +113,10 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { senderProfileCounter, accepted, requested, - hidden, blocked, verified, deleted, - alsoBestFriend, - deleteMessagesAfterXMinutes, - createdAt, - totalMediaCounter, - lastMessageSend, - lastMessageReceived, - lastFlameCounterChange, - lastFlameSync, - flameCounter + createdAt ]; @override String get aliasedName => _alias ?? actualTableName; @@ -243,10 +166,6 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { context.handle(_requestedMeta, requested.isAcceptableOrUnknown(data['requested']!, _requestedMeta)); } - if (data.containsKey('hidden')) { - context.handle(_hiddenMeta, - hidden.isAcceptableOrUnknown(data['hidden']!, _hiddenMeta)); - } if (data.containsKey('blocked')) { context.handle(_blockedMeta, blocked.isAcceptableOrUnknown(data['blocked']!, _blockedMeta)); @@ -259,29 +178,641 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { context.handle(_deletedMeta, deleted.isAcceptableOrUnknown(data['deleted']!, _deletedMeta)); } - if (data.containsKey('also_best_friend')) { - context.handle( - _alsoBestFriendMeta, - alsoBestFriend.isAcceptableOrUnknown( - data['also_best_friend']!, _alsoBestFriendMeta)); - } - if (data.containsKey('delete_messages_after_x_minutes')) { - context.handle( - _deleteMessagesAfterXMinutesMeta, - deleteMessagesAfterXMinutes.isAcceptableOrUnknown( - data['delete_messages_after_x_minutes']!, - _deleteMessagesAfterXMinutesMeta)); - } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } + return context; + } + + @override + Set get $primaryKey => {userId}; + @override + Contact map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Contact( + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + username: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}username'])!, + displayName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}display_name']), + nickName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}nick_name']), + avatarSvg: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}avatar_svg']), + senderProfileCounter: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}sender_profile_counter'])!, + accepted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}accepted'])!, + requested: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}requested'])!, + blocked: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}blocked'])!, + verified: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}verified'])!, + deleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}deleted'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + $ContactsTable createAlias(String alias) { + return $ContactsTable(attachedDatabase, alias); + } +} + +class Contact extends DataClass implements Insertable { + final int userId; + final String username; + final String? displayName; + final String? nickName; + final String? avatarSvg; + final int senderProfileCounter; + final bool accepted; + final bool requested; + final bool blocked; + final bool verified; + final bool deleted; + final DateTime createdAt; + const Contact( + {required this.userId, + required this.username, + this.displayName, + this.nickName, + this.avatarSvg, + required this.senderProfileCounter, + required this.accepted, + required this.requested, + required this.blocked, + required this.verified, + required this.deleted, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['username'] = Variable(username); + if (!nullToAbsent || displayName != null) { + map['display_name'] = Variable(displayName); + } + if (!nullToAbsent || nickName != null) { + map['nick_name'] = Variable(nickName); + } + if (!nullToAbsent || avatarSvg != null) { + map['avatar_svg'] = Variable(avatarSvg); + } + map['sender_profile_counter'] = Variable(senderProfileCounter); + map['accepted'] = Variable(accepted); + map['requested'] = Variable(requested); + map['blocked'] = Variable(blocked); + map['verified'] = Variable(verified); + map['deleted'] = Variable(deleted); + map['created_at'] = Variable(createdAt); + return map; + } + + ContactsCompanion toCompanion(bool nullToAbsent) { + return ContactsCompanion( + userId: Value(userId), + username: Value(username), + displayName: displayName == null && nullToAbsent + ? const Value.absent() + : Value(displayName), + nickName: nickName == null && nullToAbsent + ? const Value.absent() + : Value(nickName), + avatarSvg: avatarSvg == null && nullToAbsent + ? const Value.absent() + : Value(avatarSvg), + senderProfileCounter: Value(senderProfileCounter), + accepted: Value(accepted), + requested: Value(requested), + blocked: Value(blocked), + verified: Value(verified), + deleted: Value(deleted), + createdAt: Value(createdAt), + ); + } + + factory Contact.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Contact( + userId: serializer.fromJson(json['userId']), + username: serializer.fromJson(json['username']), + displayName: serializer.fromJson(json['displayName']), + nickName: serializer.fromJson(json['nickName']), + avatarSvg: serializer.fromJson(json['avatarSvg']), + senderProfileCounter: + serializer.fromJson(json['senderProfileCounter']), + accepted: serializer.fromJson(json['accepted']), + requested: serializer.fromJson(json['requested']), + blocked: serializer.fromJson(json['blocked']), + verified: serializer.fromJson(json['verified']), + deleted: serializer.fromJson(json['deleted']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'username': serializer.toJson(username), + 'displayName': serializer.toJson(displayName), + 'nickName': serializer.toJson(nickName), + 'avatarSvg': serializer.toJson(avatarSvg), + 'senderProfileCounter': serializer.toJson(senderProfileCounter), + 'accepted': serializer.toJson(accepted), + 'requested': serializer.toJson(requested), + 'blocked': serializer.toJson(blocked), + 'verified': serializer.toJson(verified), + 'deleted': serializer.toJson(deleted), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Contact copyWith( + {int? userId, + String? username, + Value displayName = const Value.absent(), + Value nickName = const Value.absent(), + Value avatarSvg = const Value.absent(), + int? senderProfileCounter, + bool? accepted, + bool? requested, + bool? blocked, + bool? verified, + bool? deleted, + DateTime? createdAt}) => + Contact( + userId: userId ?? this.userId, + username: username ?? this.username, + displayName: displayName.present ? displayName.value : this.displayName, + nickName: nickName.present ? nickName.value : this.nickName, + avatarSvg: avatarSvg.present ? avatarSvg.value : this.avatarSvg, + senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, + accepted: accepted ?? this.accepted, + requested: requested ?? this.requested, + blocked: blocked ?? this.blocked, + verified: verified ?? this.verified, + deleted: deleted ?? this.deleted, + createdAt: createdAt ?? this.createdAt, + ); + Contact copyWithCompanion(ContactsCompanion data) { + return Contact( + userId: data.userId.present ? data.userId.value : this.userId, + username: data.username.present ? data.username.value : this.username, + displayName: + data.displayName.present ? data.displayName.value : this.displayName, + nickName: data.nickName.present ? data.nickName.value : this.nickName, + avatarSvg: data.avatarSvg.present ? data.avatarSvg.value : this.avatarSvg, + senderProfileCounter: data.senderProfileCounter.present + ? data.senderProfileCounter.value + : this.senderProfileCounter, + accepted: data.accepted.present ? data.accepted.value : this.accepted, + requested: data.requested.present ? data.requested.value : this.requested, + blocked: data.blocked.present ? data.blocked.value : this.blocked, + verified: data.verified.present ? data.verified.value : this.verified, + deleted: data.deleted.present ? data.deleted.value : this.deleted, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Contact(') + ..write('userId: $userId, ') + ..write('username: $username, ') + ..write('displayName: $displayName, ') + ..write('nickName: $nickName, ') + ..write('avatarSvg: $avatarSvg, ') + ..write('senderProfileCounter: $senderProfileCounter, ') + ..write('accepted: $accepted, ') + ..write('requested: $requested, ') + ..write('blocked: $blocked, ') + ..write('verified: $verified, ') + ..write('deleted: $deleted, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + userId, + username, + displayName, + nickName, + avatarSvg, + senderProfileCounter, + accepted, + requested, + blocked, + verified, + deleted, + createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Contact && + other.userId == this.userId && + other.username == this.username && + other.displayName == this.displayName && + other.nickName == this.nickName && + other.avatarSvg == this.avatarSvg && + other.senderProfileCounter == this.senderProfileCounter && + other.accepted == this.accepted && + other.requested == this.requested && + other.blocked == this.blocked && + other.verified == this.verified && + other.deleted == this.deleted && + other.createdAt == this.createdAt); +} + +class ContactsCompanion extends UpdateCompanion { + final Value userId; + final Value username; + final Value displayName; + final Value nickName; + final Value avatarSvg; + final Value senderProfileCounter; + final Value accepted; + final Value requested; + final Value blocked; + final Value verified; + final Value deleted; + final Value createdAt; + const ContactsCompanion({ + this.userId = const Value.absent(), + this.username = const Value.absent(), + this.displayName = const Value.absent(), + this.nickName = const Value.absent(), + this.avatarSvg = const Value.absent(), + this.senderProfileCounter = const Value.absent(), + this.accepted = const Value.absent(), + this.requested = const Value.absent(), + this.blocked = const Value.absent(), + this.verified = const Value.absent(), + this.deleted = const Value.absent(), + this.createdAt = const Value.absent(), + }); + ContactsCompanion.insert({ + this.userId = const Value.absent(), + required String username, + this.displayName = const Value.absent(), + this.nickName = const Value.absent(), + this.avatarSvg = const Value.absent(), + this.senderProfileCounter = const Value.absent(), + this.accepted = const Value.absent(), + this.requested = const Value.absent(), + this.blocked = const Value.absent(), + this.verified = const Value.absent(), + this.deleted = const Value.absent(), + this.createdAt = const Value.absent(), + }) : username = Value(username); + static Insertable custom({ + Expression? userId, + Expression? username, + Expression? displayName, + Expression? nickName, + Expression? avatarSvg, + Expression? senderProfileCounter, + Expression? accepted, + Expression? requested, + Expression? blocked, + Expression? verified, + Expression? deleted, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (username != null) 'username': username, + if (displayName != null) 'display_name': displayName, + if (nickName != null) 'nick_name': nickName, + if (avatarSvg != null) 'avatar_svg': avatarSvg, + if (senderProfileCounter != null) + 'sender_profile_counter': senderProfileCounter, + if (accepted != null) 'accepted': accepted, + if (requested != null) 'requested': requested, + if (blocked != null) 'blocked': blocked, + if (verified != null) 'verified': verified, + if (deleted != null) 'deleted': deleted, + if (createdAt != null) 'created_at': createdAt, + }); + } + + ContactsCompanion copyWith( + {Value? userId, + Value? username, + Value? displayName, + Value? nickName, + Value? avatarSvg, + Value? senderProfileCounter, + Value? accepted, + Value? requested, + Value? blocked, + Value? verified, + Value? deleted, + Value? createdAt}) { + return ContactsCompanion( + userId: userId ?? this.userId, + username: username ?? this.username, + displayName: displayName ?? this.displayName, + nickName: nickName ?? this.nickName, + avatarSvg: avatarSvg ?? this.avatarSvg, + senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, + accepted: accepted ?? this.accepted, + requested: requested ?? this.requested, + blocked: blocked ?? this.blocked, + verified: verified ?? this.verified, + deleted: deleted ?? this.deleted, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (displayName.present) { + map['display_name'] = Variable(displayName.value); + } + if (nickName.present) { + map['nick_name'] = Variable(nickName.value); + } + if (avatarSvg.present) { + map['avatar_svg'] = Variable(avatarSvg.value); + } + if (senderProfileCounter.present) { + map['sender_profile_counter'] = Variable(senderProfileCounter.value); + } + if (accepted.present) { + map['accepted'] = Variable(accepted.value); + } + if (requested.present) { + map['requested'] = Variable(requested.value); + } + if (blocked.present) { + map['blocked'] = Variable(blocked.value); + } + if (verified.present) { + map['verified'] = Variable(verified.value); + } + if (deleted.present) { + map['deleted'] = Variable(deleted.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ContactsCompanion(') + ..write('userId: $userId, ') + ..write('username: $username, ') + ..write('displayName: $displayName, ') + ..write('nickName: $nickName, ') + ..write('avatarSvg: $avatarSvg, ') + ..write('senderProfileCounter: $senderProfileCounter, ') + ..write('accepted: $accepted, ') + ..write('requested: $requested, ') + ..write('blocked: $blocked, ') + ..write('verified: $verified, ') + ..write('deleted: $deleted, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GroupsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _groupIdMeta = + const VerificationMeta('groupId'); + @override + late final GeneratedColumn groupId = GeneratedColumn( + 'group_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + clientDefault: () => uuid.v4()); + static const VerificationMeta _isGroupAdminMeta = + const VerificationMeta('isGroupAdmin'); + @override + late final GeneratedColumn isGroupAdmin = GeneratedColumn( + 'is_group_admin', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_group_admin" IN (0, 1))')); + static const VerificationMeta _isDirectChatMeta = + const VerificationMeta('isDirectChat'); + @override + late final GeneratedColumn isDirectChat = GeneratedColumn( + 'is_direct_chat', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_direct_chat" IN (0, 1))')); + static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); + @override + late final GeneratedColumn pinned = GeneratedColumn( + 'pinned', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _archivedMeta = + const VerificationMeta('archived'); + @override + late final GeneratedColumn archived = GeneratedColumn( + 'archived', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("archived" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _groupNameMeta = + const VerificationMeta('groupName'); + @override + late final GeneratedColumn groupName = GeneratedColumn( + 'group_name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _totalMediaCounterMeta = + const VerificationMeta('totalMediaCounter'); + @override + late final GeneratedColumn totalMediaCounter = GeneratedColumn( + 'total_media_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _alsoBestFriendMeta = + const VerificationMeta('alsoBestFriend'); + @override + late final GeneratedColumn alsoBestFriend = GeneratedColumn( + 'also_best_friend', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("also_best_friend" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _deleteMessagesAfterMillisecondsMeta = + const VerificationMeta('deleteMessagesAfterMilliseconds'); + @override + late final GeneratedColumn deleteMessagesAfterMilliseconds = + GeneratedColumn( + 'delete_messages_after_milliseconds', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(1000 * 60 * 24)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _lastMessageSendMeta = + const VerificationMeta('lastMessageSend'); + @override + late final GeneratedColumn lastMessageSend = + GeneratedColumn('last_message_send', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _lastMessageReceivedMeta = + const VerificationMeta('lastMessageReceived'); + @override + late final GeneratedColumn lastMessageReceived = + GeneratedColumn('last_message_received', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _lastFlameCounterChangeMeta = + const VerificationMeta('lastFlameCounterChange'); + @override + late final GeneratedColumn lastFlameCounterChange = + GeneratedColumn('last_flame_counter_change', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _lastFlameSyncMeta = + const VerificationMeta('lastFlameSync'); + @override + late final GeneratedColumn lastFlameSync = + GeneratedColumn('last_flame_sync', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _flameCounterMeta = + const VerificationMeta('flameCounter'); + @override + late final GeneratedColumn flameCounter = GeneratedColumn( + 'flame_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _lastMessageExchangeMeta = + const VerificationMeta('lastMessageExchange'); + @override + late final GeneratedColumn lastMessageExchange = + GeneratedColumn('last_message_exchange', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + groupId, + isGroupAdmin, + isDirectChat, + pinned, + archived, + groupName, + totalMediaCounter, + alsoBestFriend, + deleteMessagesAfterMilliseconds, + createdAt, + lastMessageSend, + lastMessageReceived, + lastFlameCounterChange, + lastFlameSync, + flameCounter, + lastMessageExchange + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'groups'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('group_id')) { + context.handle(_groupIdMeta, + groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta)); + } + if (data.containsKey('is_group_admin')) { + context.handle( + _isGroupAdminMeta, + isGroupAdmin.isAcceptableOrUnknown( + data['is_group_admin']!, _isGroupAdminMeta)); + } else if (isInserting) { + context.missing(_isGroupAdminMeta); + } + if (data.containsKey('is_direct_chat')) { + context.handle( + _isDirectChatMeta, + isDirectChat.isAcceptableOrUnknown( + data['is_direct_chat']!, _isDirectChatMeta)); + } else if (isInserting) { + context.missing(_isDirectChatMeta); + } + if (data.containsKey('pinned')) { + context.handle(_pinnedMeta, + pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); + } + if (data.containsKey('archived')) { + context.handle(_archivedMeta, + archived.isAcceptableOrUnknown(data['archived']!, _archivedMeta)); + } + if (data.containsKey('group_name')) { + context.handle(_groupNameMeta, + groupName.isAcceptableOrUnknown(data['group_name']!, _groupNameMeta)); + } else if (isInserting) { + context.missing(_groupNameMeta); + } if (data.containsKey('total_media_counter')) { context.handle( _totalMediaCounterMeta, totalMediaCounter.isAcceptableOrUnknown( data['total_media_counter']!, _totalMediaCounterMeta)); } + if (data.containsKey('also_best_friend')) { + context.handle( + _alsoBestFriendMeta, + alsoBestFriend.isAcceptableOrUnknown( + data['also_best_friend']!, _alsoBestFriendMeta)); + } + if (data.containsKey('delete_messages_after_milliseconds')) { + context.handle( + _deleteMessagesAfterMillisecondsMeta, + deleteMessagesAfterMilliseconds.isAcceptableOrUnknown( + data['delete_messages_after_milliseconds']!, + _deleteMessagesAfterMillisecondsMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } if (data.containsKey('last_message_send')) { context.handle( _lastMessageSendMeta, @@ -312,48 +843,42 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { flameCounter.isAcceptableOrUnknown( data['flame_counter']!, _flameCounterMeta)); } + if (data.containsKey('last_message_exchange')) { + context.handle( + _lastMessageExchangeMeta, + lastMessageExchange.isAcceptableOrUnknown( + data['last_message_exchange']!, _lastMessageExchangeMeta)); + } return context; } @override - Set get $primaryKey => {userId}; + Set get $primaryKey => {groupId}; @override - Contact map(Map data, {String? tablePrefix}) { + Group map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return Contact( - userId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, - username: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}username'])!, - displayName: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}display_name']), - nickName: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}nick_name']), - avatarSvg: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}avatar_svg']), - senderProfileCounter: attachedDatabase.typeMapping.read( - DriftSqlType.int, data['${effectivePrefix}sender_profile_counter'])!, - accepted: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}accepted'])!, - requested: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}requested'])!, - hidden: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}hidden'])!, - blocked: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}blocked'])!, - verified: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}verified'])!, - deleted: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}deleted'])!, - alsoBestFriend: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}also_best_friend'])!, - deleteMessagesAfterXMinutes: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}delete_messages_after_x_minutes'])!, - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + return Group( + groupId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_id'])!, + isGroupAdmin: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_group_admin'])!, + isDirectChat: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_direct_chat'])!, + pinned: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, + archived: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}archived'])!, + groupName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_name'])!, totalMediaCounter: attachedDatabase.typeMapping.read( DriftSqlType.int, data['${effectivePrefix}total_media_counter'])!, + alsoBestFriend: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}also_best_friend'])!, + deleteMessagesAfterMilliseconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}delete_messages_after_milliseconds'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, lastMessageSend: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}last_message_send']), lastMessageReceived: attachedDatabase.typeMapping.read( @@ -366,85 +891,66 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { DriftSqlType.dateTime, data['${effectivePrefix}last_flame_sync']), flameCounter: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}flame_counter'])!, + lastMessageExchange: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_message_exchange'])!, ); } @override - $ContactsTable createAlias(String alias) { - return $ContactsTable(attachedDatabase, alias); + $GroupsTable createAlias(String alias) { + return $GroupsTable(attachedDatabase, alias); } } -class Contact extends DataClass implements Insertable { - final int userId; - final String username; - final String? displayName; - final String? nickName; - final String? avatarSvg; - final int senderProfileCounter; - final bool accepted; - final bool requested; - final bool hidden; - final bool blocked; - final bool verified; - final bool deleted; - final bool alsoBestFriend; - final int deleteMessagesAfterXMinutes; - final DateTime createdAt; +class Group extends DataClass implements Insertable { + final String groupId; + final bool isGroupAdmin; + final bool isDirectChat; + final bool pinned; + final bool archived; + final String groupName; final int totalMediaCounter; + final bool alsoBestFriend; + final int deleteMessagesAfterMilliseconds; + final DateTime createdAt; final DateTime? lastMessageSend; final DateTime? lastMessageReceived; final DateTime? lastFlameCounterChange; final DateTime? lastFlameSync; final int flameCounter; - const Contact( - {required this.userId, - required this.username, - this.displayName, - this.nickName, - this.avatarSvg, - required this.senderProfileCounter, - required this.accepted, - required this.requested, - required this.hidden, - required this.blocked, - required this.verified, - required this.deleted, - required this.alsoBestFriend, - required this.deleteMessagesAfterXMinutes, - required this.createdAt, + final DateTime lastMessageExchange; + const Group( + {required this.groupId, + required this.isGroupAdmin, + required this.isDirectChat, + required this.pinned, + required this.archived, + required this.groupName, required this.totalMediaCounter, + required this.alsoBestFriend, + required this.deleteMessagesAfterMilliseconds, + required this.createdAt, this.lastMessageSend, this.lastMessageReceived, this.lastFlameCounterChange, this.lastFlameSync, - required this.flameCounter}); + required this.flameCounter, + required this.lastMessageExchange}); @override Map toColumns(bool nullToAbsent) { final map = {}; - map['user_id'] = Variable(userId); - map['username'] = Variable(username); - if (!nullToAbsent || displayName != null) { - map['display_name'] = Variable(displayName); - } - if (!nullToAbsent || nickName != null) { - map['nick_name'] = Variable(nickName); - } - if (!nullToAbsent || avatarSvg != null) { - map['avatar_svg'] = Variable(avatarSvg); - } - map['sender_profile_counter'] = Variable(senderProfileCounter); - map['accepted'] = Variable(accepted); - map['requested'] = Variable(requested); - map['hidden'] = Variable(hidden); - map['blocked'] = Variable(blocked); - map['verified'] = Variable(verified); - map['deleted'] = Variable(deleted); - map['also_best_friend'] = Variable(alsoBestFriend); - map['delete_messages_after_x_minutes'] = - Variable(deleteMessagesAfterXMinutes); - map['created_at'] = Variable(createdAt); + map['group_id'] = Variable(groupId); + map['is_group_admin'] = Variable(isGroupAdmin); + map['is_direct_chat'] = Variable(isDirectChat); + map['pinned'] = Variable(pinned); + map['archived'] = Variable(archived); + map['group_name'] = Variable(groupName); map['total_media_counter'] = Variable(totalMediaCounter); + map['also_best_friend'] = Variable(alsoBestFriend); + map['delete_messages_after_milliseconds'] = + Variable(deleteMessagesAfterMilliseconds); + map['created_at'] = Variable(createdAt); if (!nullToAbsent || lastMessageSend != null) { map['last_message_send'] = Variable(lastMessageSend); } @@ -459,33 +965,22 @@ class Contact extends DataClass implements Insertable { map['last_flame_sync'] = Variable(lastFlameSync); } map['flame_counter'] = Variable(flameCounter); + map['last_message_exchange'] = Variable(lastMessageExchange); return map; } - ContactsCompanion toCompanion(bool nullToAbsent) { - return ContactsCompanion( - userId: Value(userId), - username: Value(username), - displayName: displayName == null && nullToAbsent - ? const Value.absent() - : Value(displayName), - nickName: nickName == null && nullToAbsent - ? const Value.absent() - : Value(nickName), - avatarSvg: avatarSvg == null && nullToAbsent - ? const Value.absent() - : Value(avatarSvg), - senderProfileCounter: Value(senderProfileCounter), - accepted: Value(accepted), - requested: Value(requested), - hidden: Value(hidden), - blocked: Value(blocked), - verified: Value(verified), - deleted: Value(deleted), - alsoBestFriend: Value(alsoBestFriend), - deleteMessagesAfterXMinutes: Value(deleteMessagesAfterXMinutes), - createdAt: Value(createdAt), + GroupsCompanion toCompanion(bool nullToAbsent) { + return GroupsCompanion( + groupId: Value(groupId), + isGroupAdmin: Value(isGroupAdmin), + isDirectChat: Value(isDirectChat), + pinned: Value(pinned), + archived: Value(archived), + groupName: Value(groupName), totalMediaCounter: Value(totalMediaCounter), + alsoBestFriend: Value(alsoBestFriend), + deleteMessagesAfterMilliseconds: Value(deleteMessagesAfterMilliseconds), + createdAt: Value(createdAt), lastMessageSend: lastMessageSend == null && nullToAbsent ? const Value.absent() : Value(lastMessageSend), @@ -499,31 +994,25 @@ class Contact extends DataClass implements Insertable { ? const Value.absent() : Value(lastFlameSync), flameCounter: Value(flameCounter), + lastMessageExchange: Value(lastMessageExchange), ); } - factory Contact.fromJson(Map json, + factory Group.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; - return Contact( - userId: serializer.fromJson(json['userId']), - username: serializer.fromJson(json['username']), - displayName: serializer.fromJson(json['displayName']), - nickName: serializer.fromJson(json['nickName']), - avatarSvg: serializer.fromJson(json['avatarSvg']), - senderProfileCounter: - serializer.fromJson(json['senderProfileCounter']), - accepted: serializer.fromJson(json['accepted']), - requested: serializer.fromJson(json['requested']), - hidden: serializer.fromJson(json['hidden']), - blocked: serializer.fromJson(json['blocked']), - verified: serializer.fromJson(json['verified']), - deleted: serializer.fromJson(json['deleted']), - alsoBestFriend: serializer.fromJson(json['alsoBestFriend']), - deleteMessagesAfterXMinutes: - serializer.fromJson(json['deleteMessagesAfterXMinutes']), - createdAt: serializer.fromJson(json['createdAt']), + return Group( + groupId: serializer.fromJson(json['groupId']), + isGroupAdmin: serializer.fromJson(json['isGroupAdmin']), + isDirectChat: serializer.fromJson(json['isDirectChat']), + pinned: serializer.fromJson(json['pinned']), + archived: serializer.fromJson(json['archived']), + groupName: serializer.fromJson(json['groupName']), totalMediaCounter: serializer.fromJson(json['totalMediaCounter']), + alsoBestFriend: serializer.fromJson(json['alsoBestFriend']), + deleteMessagesAfterMilliseconds: + serializer.fromJson(json['deleteMessagesAfterMilliseconds']), + createdAt: serializer.fromJson(json['createdAt']), lastMessageSend: serializer.fromJson(json['lastMessageSend']), lastMessageReceived: serializer.fromJson(json['lastMessageReceived']), @@ -531,78 +1020,64 @@ class Contact extends DataClass implements Insertable { serializer.fromJson(json['lastFlameCounterChange']), lastFlameSync: serializer.fromJson(json['lastFlameSync']), flameCounter: serializer.fromJson(json['flameCounter']), + lastMessageExchange: + serializer.fromJson(json['lastMessageExchange']), ); } @override Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'userId': serializer.toJson(userId), - 'username': serializer.toJson(username), - 'displayName': serializer.toJson(displayName), - 'nickName': serializer.toJson(nickName), - 'avatarSvg': serializer.toJson(avatarSvg), - 'senderProfileCounter': serializer.toJson(senderProfileCounter), - 'accepted': serializer.toJson(accepted), - 'requested': serializer.toJson(requested), - 'hidden': serializer.toJson(hidden), - 'blocked': serializer.toJson(blocked), - 'verified': serializer.toJson(verified), - 'deleted': serializer.toJson(deleted), - 'alsoBestFriend': serializer.toJson(alsoBestFriend), - 'deleteMessagesAfterXMinutes': - serializer.toJson(deleteMessagesAfterXMinutes), - 'createdAt': serializer.toJson(createdAt), + 'groupId': serializer.toJson(groupId), + 'isGroupAdmin': serializer.toJson(isGroupAdmin), + 'isDirectChat': serializer.toJson(isDirectChat), + 'pinned': serializer.toJson(pinned), + 'archived': serializer.toJson(archived), + 'groupName': serializer.toJson(groupName), 'totalMediaCounter': serializer.toJson(totalMediaCounter), + 'alsoBestFriend': serializer.toJson(alsoBestFriend), + 'deleteMessagesAfterMilliseconds': + serializer.toJson(deleteMessagesAfterMilliseconds), + 'createdAt': serializer.toJson(createdAt), 'lastMessageSend': serializer.toJson(lastMessageSend), 'lastMessageReceived': serializer.toJson(lastMessageReceived), 'lastFlameCounterChange': serializer.toJson(lastFlameCounterChange), 'lastFlameSync': serializer.toJson(lastFlameSync), 'flameCounter': serializer.toJson(flameCounter), + 'lastMessageExchange': serializer.toJson(lastMessageExchange), }; } - Contact copyWith( - {int? userId, - String? username, - Value displayName = const Value.absent(), - Value nickName = const Value.absent(), - Value avatarSvg = const Value.absent(), - int? senderProfileCounter, - bool? accepted, - bool? requested, - bool? hidden, - bool? blocked, - bool? verified, - bool? deleted, - bool? alsoBestFriend, - int? deleteMessagesAfterXMinutes, - DateTime? createdAt, + Group copyWith( + {String? groupId, + bool? isGroupAdmin, + bool? isDirectChat, + bool? pinned, + bool? archived, + String? groupName, int? totalMediaCounter, + bool? alsoBestFriend, + int? deleteMessagesAfterMilliseconds, + DateTime? createdAt, Value lastMessageSend = const Value.absent(), Value lastMessageReceived = const Value.absent(), Value lastFlameCounterChange = const Value.absent(), Value lastFlameSync = const Value.absent(), - int? flameCounter}) => - Contact( - userId: userId ?? this.userId, - username: username ?? this.username, - displayName: displayName.present ? displayName.value : this.displayName, - nickName: nickName.present ? nickName.value : this.nickName, - avatarSvg: avatarSvg.present ? avatarSvg.value : this.avatarSvg, - senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, - accepted: accepted ?? this.accepted, - requested: requested ?? this.requested, - hidden: hidden ?? this.hidden, - blocked: blocked ?? this.blocked, - verified: verified ?? this.verified, - deleted: deleted ?? this.deleted, - alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend, - deleteMessagesAfterXMinutes: - deleteMessagesAfterXMinutes ?? this.deleteMessagesAfterXMinutes, - createdAt: createdAt ?? this.createdAt, + int? flameCounter, + DateTime? lastMessageExchange}) => + Group( + groupId: groupId ?? this.groupId, + isGroupAdmin: isGroupAdmin ?? this.isGroupAdmin, + isDirectChat: isDirectChat ?? this.isDirectChat, + pinned: pinned ?? this.pinned, + archived: archived ?? this.archived, + groupName: groupName ?? this.groupName, totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter, + alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend, + deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds ?? + this.deleteMessagesAfterMilliseconds, + createdAt: createdAt ?? this.createdAt, lastMessageSend: lastMessageSend.present ? lastMessageSend.value : this.lastMessageSend, @@ -615,34 +1090,31 @@ class Contact extends DataClass implements Insertable { lastFlameSync: lastFlameSync.present ? lastFlameSync.value : this.lastFlameSync, flameCounter: flameCounter ?? this.flameCounter, + lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, ); - Contact copyWithCompanion(ContactsCompanion data) { - return Contact( - userId: data.userId.present ? data.userId.value : this.userId, - username: data.username.present ? data.username.value : this.username, - displayName: - data.displayName.present ? data.displayName.value : this.displayName, - nickName: data.nickName.present ? data.nickName.value : this.nickName, - avatarSvg: data.avatarSvg.present ? data.avatarSvg.value : this.avatarSvg, - senderProfileCounter: data.senderProfileCounter.present - ? data.senderProfileCounter.value - : this.senderProfileCounter, - accepted: data.accepted.present ? data.accepted.value : this.accepted, - requested: data.requested.present ? data.requested.value : this.requested, - hidden: data.hidden.present ? data.hidden.value : this.hidden, - blocked: data.blocked.present ? data.blocked.value : this.blocked, - verified: data.verified.present ? data.verified.value : this.verified, - deleted: data.deleted.present ? data.deleted.value : this.deleted, - alsoBestFriend: data.alsoBestFriend.present - ? data.alsoBestFriend.value - : this.alsoBestFriend, - deleteMessagesAfterXMinutes: data.deleteMessagesAfterXMinutes.present - ? data.deleteMessagesAfterXMinutes.value - : this.deleteMessagesAfterXMinutes, - createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + Group copyWithCompanion(GroupsCompanion data) { + return Group( + groupId: data.groupId.present ? data.groupId.value : this.groupId, + isGroupAdmin: data.isGroupAdmin.present + ? data.isGroupAdmin.value + : this.isGroupAdmin, + isDirectChat: data.isDirectChat.present + ? data.isDirectChat.value + : this.isDirectChat, + pinned: data.pinned.present ? data.pinned.value : this.pinned, + archived: data.archived.present ? data.archived.value : this.archived, + groupName: data.groupName.present ? data.groupName.value : this.groupName, totalMediaCounter: data.totalMediaCounter.present ? data.totalMediaCounter.value : this.totalMediaCounter, + alsoBestFriend: data.alsoBestFriend.present + ? data.alsoBestFriend.value + : this.alsoBestFriend, + deleteMessagesAfterMilliseconds: + data.deleteMessagesAfterMilliseconds.present + ? data.deleteMessagesAfterMilliseconds.value + : this.deleteMessagesAfterMilliseconds, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, lastMessageSend: data.lastMessageSend.present ? data.lastMessageSend.value : this.lastMessageSend, @@ -658,199 +1130,166 @@ class Contact extends DataClass implements Insertable { flameCounter: data.flameCounter.present ? data.flameCounter.value : this.flameCounter, + lastMessageExchange: data.lastMessageExchange.present + ? data.lastMessageExchange.value + : this.lastMessageExchange, ); } @override String toString() { - return (StringBuffer('Contact(') - ..write('userId: $userId, ') - ..write('username: $username, ') - ..write('displayName: $displayName, ') - ..write('nickName: $nickName, ') - ..write('avatarSvg: $avatarSvg, ') - ..write('senderProfileCounter: $senderProfileCounter, ') - ..write('accepted: $accepted, ') - ..write('requested: $requested, ') - ..write('hidden: $hidden, ') - ..write('blocked: $blocked, ') - ..write('verified: $verified, ') - ..write('deleted: $deleted, ') - ..write('alsoBestFriend: $alsoBestFriend, ') - ..write('deleteMessagesAfterXMinutes: $deleteMessagesAfterXMinutes, ') - ..write('createdAt: $createdAt, ') + return (StringBuffer('Group(') + ..write('groupId: $groupId, ') + ..write('isGroupAdmin: $isGroupAdmin, ') + ..write('isDirectChat: $isDirectChat, ') + ..write('pinned: $pinned, ') + ..write('archived: $archived, ') + ..write('groupName: $groupName, ') ..write('totalMediaCounter: $totalMediaCounter, ') + ..write('alsoBestFriend: $alsoBestFriend, ') + ..write( + 'deleteMessagesAfterMilliseconds: $deleteMessagesAfterMilliseconds, ') + ..write('createdAt: $createdAt, ') ..write('lastMessageSend: $lastMessageSend, ') ..write('lastMessageReceived: $lastMessageReceived, ') ..write('lastFlameCounterChange: $lastFlameCounterChange, ') ..write('lastFlameSync: $lastFlameSync, ') - ..write('flameCounter: $flameCounter') + ..write('flameCounter: $flameCounter, ') + ..write('lastMessageExchange: $lastMessageExchange') ..write(')')) .toString(); } @override - int get hashCode => Object.hashAll([ - userId, - username, - displayName, - nickName, - avatarSvg, - senderProfileCounter, - accepted, - requested, - hidden, - blocked, - verified, - deleted, - alsoBestFriend, - deleteMessagesAfterXMinutes, - createdAt, - totalMediaCounter, - lastMessageSend, - lastMessageReceived, - lastFlameCounterChange, - lastFlameSync, - flameCounter - ]); + int get hashCode => Object.hash( + groupId, + isGroupAdmin, + isDirectChat, + pinned, + archived, + groupName, + totalMediaCounter, + alsoBestFriend, + deleteMessagesAfterMilliseconds, + createdAt, + lastMessageSend, + lastMessageReceived, + lastFlameCounterChange, + lastFlameSync, + flameCounter, + lastMessageExchange); @override bool operator ==(Object other) => identical(this, other) || - (other is Contact && - other.userId == this.userId && - other.username == this.username && - other.displayName == this.displayName && - other.nickName == this.nickName && - other.avatarSvg == this.avatarSvg && - other.senderProfileCounter == this.senderProfileCounter && - other.accepted == this.accepted && - other.requested == this.requested && - other.hidden == this.hidden && - other.blocked == this.blocked && - other.verified == this.verified && - other.deleted == this.deleted && - other.alsoBestFriend == this.alsoBestFriend && - other.deleteMessagesAfterXMinutes == - this.deleteMessagesAfterXMinutes && - other.createdAt == this.createdAt && + (other is Group && + other.groupId == this.groupId && + other.isGroupAdmin == this.isGroupAdmin && + other.isDirectChat == this.isDirectChat && + other.pinned == this.pinned && + other.archived == this.archived && + other.groupName == this.groupName && other.totalMediaCounter == this.totalMediaCounter && + other.alsoBestFriend == this.alsoBestFriend && + other.deleteMessagesAfterMilliseconds == + this.deleteMessagesAfterMilliseconds && + other.createdAt == this.createdAt && other.lastMessageSend == this.lastMessageSend && other.lastMessageReceived == this.lastMessageReceived && other.lastFlameCounterChange == this.lastFlameCounterChange && other.lastFlameSync == this.lastFlameSync && - other.flameCounter == this.flameCounter); + other.flameCounter == this.flameCounter && + other.lastMessageExchange == this.lastMessageExchange); } -class ContactsCompanion extends UpdateCompanion { - final Value userId; - final Value username; - final Value displayName; - final Value nickName; - final Value avatarSvg; - final Value senderProfileCounter; - final Value accepted; - final Value requested; - final Value hidden; - final Value blocked; - final Value verified; - final Value deleted; - final Value alsoBestFriend; - final Value deleteMessagesAfterXMinutes; - final Value createdAt; +class GroupsCompanion extends UpdateCompanion { + final Value groupId; + final Value isGroupAdmin; + final Value isDirectChat; + final Value pinned; + final Value archived; + final Value groupName; final Value totalMediaCounter; + final Value alsoBestFriend; + final Value deleteMessagesAfterMilliseconds; + final Value createdAt; final Value lastMessageSend; final Value lastMessageReceived; final Value lastFlameCounterChange; final Value lastFlameSync; final Value flameCounter; - const ContactsCompanion({ - this.userId = const Value.absent(), - this.username = const Value.absent(), - this.displayName = const Value.absent(), - this.nickName = const Value.absent(), - this.avatarSvg = const Value.absent(), - this.senderProfileCounter = const Value.absent(), - this.accepted = const Value.absent(), - this.requested = const Value.absent(), - this.hidden = const Value.absent(), - this.blocked = const Value.absent(), - this.verified = const Value.absent(), - this.deleted = const Value.absent(), - this.alsoBestFriend = const Value.absent(), - this.deleteMessagesAfterXMinutes = const Value.absent(), - this.createdAt = const Value.absent(), + final Value lastMessageExchange; + final Value rowid; + const GroupsCompanion({ + this.groupId = const Value.absent(), + this.isGroupAdmin = const Value.absent(), + this.isDirectChat = const Value.absent(), + this.pinned = const Value.absent(), + this.archived = const Value.absent(), + this.groupName = const Value.absent(), this.totalMediaCounter = const Value.absent(), + this.alsoBestFriend = const Value.absent(), + this.deleteMessagesAfterMilliseconds = const Value.absent(), + this.createdAt = const Value.absent(), this.lastMessageSend = const Value.absent(), this.lastMessageReceived = const Value.absent(), this.lastFlameCounterChange = const Value.absent(), this.lastFlameSync = const Value.absent(), this.flameCounter = const Value.absent(), + this.lastMessageExchange = const Value.absent(), + this.rowid = const Value.absent(), }); - ContactsCompanion.insert({ - this.userId = const Value.absent(), - required String username, - this.displayName = const Value.absent(), - this.nickName = const Value.absent(), - this.avatarSvg = const Value.absent(), - this.senderProfileCounter = const Value.absent(), - this.accepted = const Value.absent(), - this.requested = const Value.absent(), - this.hidden = const Value.absent(), - this.blocked = const Value.absent(), - this.verified = const Value.absent(), - this.deleted = const Value.absent(), - this.alsoBestFriend = const Value.absent(), - this.deleteMessagesAfterXMinutes = const Value.absent(), - this.createdAt = const Value.absent(), + GroupsCompanion.insert({ + this.groupId = const Value.absent(), + required bool isGroupAdmin, + required bool isDirectChat, + this.pinned = const Value.absent(), + this.archived = const Value.absent(), + required String groupName, this.totalMediaCounter = const Value.absent(), + this.alsoBestFriend = const Value.absent(), + this.deleteMessagesAfterMilliseconds = const Value.absent(), + this.createdAt = const Value.absent(), this.lastMessageSend = const Value.absent(), this.lastMessageReceived = const Value.absent(), this.lastFlameCounterChange = const Value.absent(), this.lastFlameSync = const Value.absent(), this.flameCounter = const Value.absent(), - }) : username = Value(username); - static Insertable custom({ - Expression? userId, - Expression? username, - Expression? displayName, - Expression? nickName, - Expression? avatarSvg, - Expression? senderProfileCounter, - Expression? accepted, - Expression? requested, - Expression? hidden, - Expression? blocked, - Expression? verified, - Expression? deleted, - Expression? alsoBestFriend, - Expression? deleteMessagesAfterXMinutes, - Expression? createdAt, + this.lastMessageExchange = const Value.absent(), + this.rowid = const Value.absent(), + }) : isGroupAdmin = Value(isGroupAdmin), + isDirectChat = Value(isDirectChat), + groupName = Value(groupName); + static Insertable custom({ + Expression? groupId, + Expression? isGroupAdmin, + Expression? isDirectChat, + Expression? pinned, + Expression? archived, + Expression? groupName, Expression? totalMediaCounter, + Expression? alsoBestFriend, + Expression? deleteMessagesAfterMilliseconds, + Expression? createdAt, Expression? lastMessageSend, Expression? lastMessageReceived, Expression? lastFlameCounterChange, Expression? lastFlameSync, Expression? flameCounter, + Expression? lastMessageExchange, + Expression? rowid, }) { return RawValuesInsertable({ - if (userId != null) 'user_id': userId, - if (username != null) 'username': username, - if (displayName != null) 'display_name': displayName, - if (nickName != null) 'nick_name': nickName, - if (avatarSvg != null) 'avatar_svg': avatarSvg, - if (senderProfileCounter != null) - 'sender_profile_counter': senderProfileCounter, - if (accepted != null) 'accepted': accepted, - if (requested != null) 'requested': requested, - if (hidden != null) 'hidden': hidden, - if (blocked != null) 'blocked': blocked, - if (verified != null) 'verified': verified, - if (deleted != null) 'deleted': deleted, - if (alsoBestFriend != null) 'also_best_friend': alsoBestFriend, - if (deleteMessagesAfterXMinutes != null) - 'delete_messages_after_x_minutes': deleteMessagesAfterXMinutes, - if (createdAt != null) 'created_at': createdAt, + if (groupId != null) 'group_id': groupId, + if (isGroupAdmin != null) 'is_group_admin': isGroupAdmin, + if (isDirectChat != null) 'is_direct_chat': isDirectChat, + if (pinned != null) 'pinned': pinned, + if (archived != null) 'archived': archived, + if (groupName != null) 'group_name': groupName, if (totalMediaCounter != null) 'total_media_counter': totalMediaCounter, + if (alsoBestFriend != null) 'also_best_friend': alsoBestFriend, + if (deleteMessagesAfterMilliseconds != null) + 'delete_messages_after_milliseconds': deleteMessagesAfterMilliseconds, + if (createdAt != null) 'created_at': createdAt, if (lastMessageSend != null) 'last_message_send': lastMessageSend, if (lastMessageReceived != null) 'last_message_received': lastMessageReceived, @@ -858,110 +1297,87 @@ class ContactsCompanion extends UpdateCompanion { 'last_flame_counter_change': lastFlameCounterChange, if (lastFlameSync != null) 'last_flame_sync': lastFlameSync, if (flameCounter != null) 'flame_counter': flameCounter, + if (lastMessageExchange != null) + 'last_message_exchange': lastMessageExchange, + if (rowid != null) 'rowid': rowid, }); } - ContactsCompanion copyWith( - {Value? userId, - Value? username, - Value? displayName, - Value? nickName, - Value? avatarSvg, - Value? senderProfileCounter, - Value? accepted, - Value? requested, - Value? hidden, - Value? blocked, - Value? verified, - Value? deleted, - Value? alsoBestFriend, - Value? deleteMessagesAfterXMinutes, - Value? createdAt, + GroupsCompanion copyWith( + {Value? groupId, + Value? isGroupAdmin, + Value? isDirectChat, + Value? pinned, + Value? archived, + Value? groupName, Value? totalMediaCounter, + Value? alsoBestFriend, + Value? deleteMessagesAfterMilliseconds, + Value? createdAt, Value? lastMessageSend, Value? lastMessageReceived, Value? lastFlameCounterChange, Value? lastFlameSync, - Value? flameCounter}) { - return ContactsCompanion( - userId: userId ?? this.userId, - username: username ?? this.username, - displayName: displayName ?? this.displayName, - nickName: nickName ?? this.nickName, - avatarSvg: avatarSvg ?? this.avatarSvg, - senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, - accepted: accepted ?? this.accepted, - requested: requested ?? this.requested, - hidden: hidden ?? this.hidden, - blocked: blocked ?? this.blocked, - verified: verified ?? this.verified, - deleted: deleted ?? this.deleted, - alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend, - deleteMessagesAfterXMinutes: - deleteMessagesAfterXMinutes ?? this.deleteMessagesAfterXMinutes, - createdAt: createdAt ?? this.createdAt, + Value? flameCounter, + Value? lastMessageExchange, + Value? rowid}) { + return GroupsCompanion( + groupId: groupId ?? this.groupId, + isGroupAdmin: isGroupAdmin ?? this.isGroupAdmin, + isDirectChat: isDirectChat ?? this.isDirectChat, + pinned: pinned ?? this.pinned, + archived: archived ?? this.archived, + groupName: groupName ?? this.groupName, totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter, + alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend, + deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds ?? + this.deleteMessagesAfterMilliseconds, + createdAt: createdAt ?? this.createdAt, lastMessageSend: lastMessageSend ?? this.lastMessageSend, lastMessageReceived: lastMessageReceived ?? this.lastMessageReceived, lastFlameCounterChange: lastFlameCounterChange ?? this.lastFlameCounterChange, lastFlameSync: lastFlameSync ?? this.lastFlameSync, flameCounter: flameCounter ?? this.flameCounter, + lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, + rowid: rowid ?? this.rowid, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (userId.present) { - map['user_id'] = Variable(userId.value); + if (groupId.present) { + map['group_id'] = Variable(groupId.value); } - if (username.present) { - map['username'] = Variable(username.value); + if (isGroupAdmin.present) { + map['is_group_admin'] = Variable(isGroupAdmin.value); } - if (displayName.present) { - map['display_name'] = Variable(displayName.value); + if (isDirectChat.present) { + map['is_direct_chat'] = Variable(isDirectChat.value); } - if (nickName.present) { - map['nick_name'] = Variable(nickName.value); + if (pinned.present) { + map['pinned'] = Variable(pinned.value); } - if (avatarSvg.present) { - map['avatar_svg'] = Variable(avatarSvg.value); + if (archived.present) { + map['archived'] = Variable(archived.value); } - if (senderProfileCounter.present) { - map['sender_profile_counter'] = Variable(senderProfileCounter.value); + if (groupName.present) { + map['group_name'] = Variable(groupName.value); } - if (accepted.present) { - map['accepted'] = Variable(accepted.value); - } - if (requested.present) { - map['requested'] = Variable(requested.value); - } - if (hidden.present) { - map['hidden'] = Variable(hidden.value); - } - if (blocked.present) { - map['blocked'] = Variable(blocked.value); - } - if (verified.present) { - map['verified'] = Variable(verified.value); - } - if (deleted.present) { - map['deleted'] = Variable(deleted.value); + if (totalMediaCounter.present) { + map['total_media_counter'] = Variable(totalMediaCounter.value); } if (alsoBestFriend.present) { map['also_best_friend'] = Variable(alsoBestFriend.value); } - if (deleteMessagesAfterXMinutes.present) { - map['delete_messages_after_x_minutes'] = - Variable(deleteMessagesAfterXMinutes.value); + if (deleteMessagesAfterMilliseconds.present) { + map['delete_messages_after_milliseconds'] = + Variable(deleteMessagesAfterMilliseconds.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); } - if (totalMediaCounter.present) { - map['total_media_counter'] = Variable(totalMediaCounter.value); - } if (lastMessageSend.present) { map['last_message_send'] = Variable(lastMessageSend.value); } @@ -979,465 +1395,10 @@ class ContactsCompanion extends UpdateCompanion { if (flameCounter.present) { map['flame_counter'] = Variable(flameCounter.value); } - return map; - } - - @override - String toString() { - return (StringBuffer('ContactsCompanion(') - ..write('userId: $userId, ') - ..write('username: $username, ') - ..write('displayName: $displayName, ') - ..write('nickName: $nickName, ') - ..write('avatarSvg: $avatarSvg, ') - ..write('senderProfileCounter: $senderProfileCounter, ') - ..write('accepted: $accepted, ') - ..write('requested: $requested, ') - ..write('hidden: $hidden, ') - ..write('blocked: $blocked, ') - ..write('verified: $verified, ') - ..write('deleted: $deleted, ') - ..write('alsoBestFriend: $alsoBestFriend, ') - ..write('deleteMessagesAfterXMinutes: $deleteMessagesAfterXMinutes, ') - ..write('createdAt: $createdAt, ') - ..write('totalMediaCounter: $totalMediaCounter, ') - ..write('lastMessageSend: $lastMessageSend, ') - ..write('lastMessageReceived: $lastMessageReceived, ') - ..write('lastFlameCounterChange: $lastFlameCounterChange, ') - ..write('lastFlameSync: $lastFlameSync, ') - ..write('flameCounter: $flameCounter') - ..write(')')) - .toString(); - } -} - -class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $GroupsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _groupIdMeta = - const VerificationMeta('groupId'); - @override - late final GeneratedColumn groupId = GeneratedColumn( - 'group_id', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - clientDefault: () => uuid.v4()); - static const VerificationMeta _isGroupAdminMeta = - const VerificationMeta('isGroupAdmin'); - @override - late final GeneratedColumn isGroupAdmin = GeneratedColumn( - 'is_group_admin', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("is_group_admin" IN (0, 1))')); - static const VerificationMeta _isGroupOfTwoMeta = - const VerificationMeta('isGroupOfTwo'); - @override - late final GeneratedColumn isGroupOfTwo = GeneratedColumn( - 'is_group_of_two', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("is_group_of_two" IN (0, 1))')); - static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); - @override - late final GeneratedColumn pinned = GeneratedColumn( - 'pinned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _archivedMeta = - const VerificationMeta('archived'); - @override - late final GeneratedColumn archived = GeneratedColumn( - 'archived', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("archived" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _groupNameMeta = - const VerificationMeta('groupName'); - @override - late final GeneratedColumn groupName = GeneratedColumn( - 'group_name', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _lastMessageExchangeMeta = - const VerificationMeta('lastMessageExchange'); - @override - late final GeneratedColumn lastMessageExchange = - GeneratedColumn('last_message_exchange', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); - @override - late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - @override - List get $columns => [ - groupId, - isGroupAdmin, - isGroupOfTwo, - pinned, - archived, - groupName, - lastMessageExchange, - createdAt - ]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'groups'; - @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('group_id')) { - context.handle(_groupIdMeta, - groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta)); - } - if (data.containsKey('is_group_admin')) { - context.handle( - _isGroupAdminMeta, - isGroupAdmin.isAcceptableOrUnknown( - data['is_group_admin']!, _isGroupAdminMeta)); - } else if (isInserting) { - context.missing(_isGroupAdminMeta); - } - if (data.containsKey('is_group_of_two')) { - context.handle( - _isGroupOfTwoMeta, - isGroupOfTwo.isAcceptableOrUnknown( - data['is_group_of_two']!, _isGroupOfTwoMeta)); - } else if (isInserting) { - context.missing(_isGroupOfTwoMeta); - } - if (data.containsKey('pinned')) { - context.handle(_pinnedMeta, - pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); - } - if (data.containsKey('archived')) { - context.handle(_archivedMeta, - archived.isAcceptableOrUnknown(data['archived']!, _archivedMeta)); - } - if (data.containsKey('group_name')) { - context.handle(_groupNameMeta, - groupName.isAcceptableOrUnknown(data['group_name']!, _groupNameMeta)); - } else if (isInserting) { - context.missing(_groupNameMeta); - } - if (data.containsKey('last_message_exchange')) { - context.handle( - _lastMessageExchangeMeta, - lastMessageExchange.isAcceptableOrUnknown( - data['last_message_exchange']!, _lastMessageExchangeMeta)); - } - if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); - } - return context; - } - - @override - Set get $primaryKey => {groupId}; - @override - Group map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return Group( - groupId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}group_id'])!, - isGroupAdmin: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}is_group_admin'])!, - isGroupOfTwo: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}is_group_of_two'])!, - pinned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, - archived: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}archived'])!, - groupName: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}group_name'])!, - lastMessageExchange: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, - data['${effectivePrefix}last_message_exchange'])!, - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - ); - } - - @override - $GroupsTable createAlias(String alias) { - return $GroupsTable(attachedDatabase, alias); - } -} - -class Group extends DataClass implements Insertable { - final String groupId; - final bool isGroupAdmin; - final bool isGroupOfTwo; - final bool pinned; - final bool archived; - final String groupName; - final DateTime lastMessageExchange; - final DateTime createdAt; - const Group( - {required this.groupId, - required this.isGroupAdmin, - required this.isGroupOfTwo, - required this.pinned, - required this.archived, - required this.groupName, - required this.lastMessageExchange, - required this.createdAt}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['group_id'] = Variable(groupId); - map['is_group_admin'] = Variable(isGroupAdmin); - map['is_group_of_two'] = Variable(isGroupOfTwo); - map['pinned'] = Variable(pinned); - map['archived'] = Variable(archived); - map['group_name'] = Variable(groupName); - map['last_message_exchange'] = Variable(lastMessageExchange); - map['created_at'] = Variable(createdAt); - return map; - } - - GroupsCompanion toCompanion(bool nullToAbsent) { - return GroupsCompanion( - groupId: Value(groupId), - isGroupAdmin: Value(isGroupAdmin), - isGroupOfTwo: Value(isGroupOfTwo), - pinned: Value(pinned), - archived: Value(archived), - groupName: Value(groupName), - lastMessageExchange: Value(lastMessageExchange), - createdAt: Value(createdAt), - ); - } - - factory Group.fromJson(Map json, - {ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return Group( - groupId: serializer.fromJson(json['groupId']), - isGroupAdmin: serializer.fromJson(json['isGroupAdmin']), - isGroupOfTwo: serializer.fromJson(json['isGroupOfTwo']), - pinned: serializer.fromJson(json['pinned']), - archived: serializer.fromJson(json['archived']), - groupName: serializer.fromJson(json['groupName']), - lastMessageExchange: - serializer.fromJson(json['lastMessageExchange']), - createdAt: serializer.fromJson(json['createdAt']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'groupId': serializer.toJson(groupId), - 'isGroupAdmin': serializer.toJson(isGroupAdmin), - 'isGroupOfTwo': serializer.toJson(isGroupOfTwo), - 'pinned': serializer.toJson(pinned), - 'archived': serializer.toJson(archived), - 'groupName': serializer.toJson(groupName), - 'lastMessageExchange': serializer.toJson(lastMessageExchange), - 'createdAt': serializer.toJson(createdAt), - }; - } - - Group copyWith( - {String? groupId, - bool? isGroupAdmin, - bool? isGroupOfTwo, - bool? pinned, - bool? archived, - String? groupName, - DateTime? lastMessageExchange, - DateTime? createdAt}) => - Group( - groupId: groupId ?? this.groupId, - isGroupAdmin: isGroupAdmin ?? this.isGroupAdmin, - isGroupOfTwo: isGroupOfTwo ?? this.isGroupOfTwo, - pinned: pinned ?? this.pinned, - archived: archived ?? this.archived, - groupName: groupName ?? this.groupName, - lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, - createdAt: createdAt ?? this.createdAt, - ); - Group copyWithCompanion(GroupsCompanion data) { - return Group( - groupId: data.groupId.present ? data.groupId.value : this.groupId, - isGroupAdmin: data.isGroupAdmin.present - ? data.isGroupAdmin.value - : this.isGroupAdmin, - isGroupOfTwo: data.isGroupOfTwo.present - ? data.isGroupOfTwo.value - : this.isGroupOfTwo, - pinned: data.pinned.present ? data.pinned.value : this.pinned, - archived: data.archived.present ? data.archived.value : this.archived, - groupName: data.groupName.present ? data.groupName.value : this.groupName, - lastMessageExchange: data.lastMessageExchange.present - ? data.lastMessageExchange.value - : this.lastMessageExchange, - createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, - ); - } - - @override - String toString() { - return (StringBuffer('Group(') - ..write('groupId: $groupId, ') - ..write('isGroupAdmin: $isGroupAdmin, ') - ..write('isGroupOfTwo: $isGroupOfTwo, ') - ..write('pinned: $pinned, ') - ..write('archived: $archived, ') - ..write('groupName: $groupName, ') - ..write('lastMessageExchange: $lastMessageExchange, ') - ..write('createdAt: $createdAt') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(groupId, isGroupAdmin, isGroupOfTwo, pinned, - archived, groupName, lastMessageExchange, createdAt); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is Group && - other.groupId == this.groupId && - other.isGroupAdmin == this.isGroupAdmin && - other.isGroupOfTwo == this.isGroupOfTwo && - other.pinned == this.pinned && - other.archived == this.archived && - other.groupName == this.groupName && - other.lastMessageExchange == this.lastMessageExchange && - other.createdAt == this.createdAt); -} - -class GroupsCompanion extends UpdateCompanion { - final Value groupId; - final Value isGroupAdmin; - final Value isGroupOfTwo; - final Value pinned; - final Value archived; - final Value groupName; - final Value lastMessageExchange; - final Value createdAt; - final Value rowid; - const GroupsCompanion({ - this.groupId = const Value.absent(), - this.isGroupAdmin = const Value.absent(), - this.isGroupOfTwo = const Value.absent(), - this.pinned = const Value.absent(), - this.archived = const Value.absent(), - this.groupName = const Value.absent(), - this.lastMessageExchange = const Value.absent(), - this.createdAt = const Value.absent(), - this.rowid = const Value.absent(), - }); - GroupsCompanion.insert({ - this.groupId = const Value.absent(), - required bool isGroupAdmin, - required bool isGroupOfTwo, - this.pinned = const Value.absent(), - this.archived = const Value.absent(), - required String groupName, - this.lastMessageExchange = const Value.absent(), - this.createdAt = const Value.absent(), - this.rowid = const Value.absent(), - }) : isGroupAdmin = Value(isGroupAdmin), - isGroupOfTwo = Value(isGroupOfTwo), - groupName = Value(groupName); - static Insertable custom({ - Expression? groupId, - Expression? isGroupAdmin, - Expression? isGroupOfTwo, - Expression? pinned, - Expression? archived, - Expression? groupName, - Expression? lastMessageExchange, - Expression? createdAt, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (groupId != null) 'group_id': groupId, - if (isGroupAdmin != null) 'is_group_admin': isGroupAdmin, - if (isGroupOfTwo != null) 'is_group_of_two': isGroupOfTwo, - if (pinned != null) 'pinned': pinned, - if (archived != null) 'archived': archived, - if (groupName != null) 'group_name': groupName, - if (lastMessageExchange != null) - 'last_message_exchange': lastMessageExchange, - if (createdAt != null) 'created_at': createdAt, - if (rowid != null) 'rowid': rowid, - }); - } - - GroupsCompanion copyWith( - {Value? groupId, - Value? isGroupAdmin, - Value? isGroupOfTwo, - Value? pinned, - Value? archived, - Value? groupName, - Value? lastMessageExchange, - Value? createdAt, - Value? rowid}) { - return GroupsCompanion( - groupId: groupId ?? this.groupId, - isGroupAdmin: isGroupAdmin ?? this.isGroupAdmin, - isGroupOfTwo: isGroupOfTwo ?? this.isGroupOfTwo, - pinned: pinned ?? this.pinned, - archived: archived ?? this.archived, - groupName: groupName ?? this.groupName, - lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, - createdAt: createdAt ?? this.createdAt, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (groupId.present) { - map['group_id'] = Variable(groupId.value); - } - if (isGroupAdmin.present) { - map['is_group_admin'] = Variable(isGroupAdmin.value); - } - if (isGroupOfTwo.present) { - map['is_group_of_two'] = Variable(isGroupOfTwo.value); - } - if (pinned.present) { - map['pinned'] = Variable(pinned.value); - } - if (archived.present) { - map['archived'] = Variable(archived.value); - } - if (groupName.present) { - map['group_name'] = Variable(groupName.value); - } if (lastMessageExchange.present) { map['last_message_exchange'] = Variable(lastMessageExchange.value); } - if (createdAt.present) { - map['created_at'] = Variable(createdAt.value); - } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -1449,12 +1410,21 @@ class GroupsCompanion extends UpdateCompanion { return (StringBuffer('GroupsCompanion(') ..write('groupId: $groupId, ') ..write('isGroupAdmin: $isGroupAdmin, ') - ..write('isGroupOfTwo: $isGroupOfTwo, ') + ..write('isDirectChat: $isDirectChat, ') ..write('pinned: $pinned, ') ..write('archived: $archived, ') ..write('groupName: $groupName, ') - ..write('lastMessageExchange: $lastMessageExchange, ') + ..write('totalMediaCounter: $totalMediaCounter, ') + ..write('alsoBestFriend: $alsoBestFriend, ') + ..write( + 'deleteMessagesAfterMilliseconds: $deleteMessagesAfterMilliseconds, ') ..write('createdAt: $createdAt, ') + ..write('lastMessageSend: $lastMessageSend, ') + ..write('lastMessageReceived: $lastMessageReceived, ') + ..write('lastFlameCounterChange: $lastFlameCounterChange, ') + ..write('lastFlameSync: $lastFlameSync, ') + ..write('flameCounter: $flameCounter, ') + ..write('lastMessageExchange: $lastMessageExchange, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -2309,16 +2279,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("is_deleted_from_sender" IN (0, 1))'), defaultValue: const Constant(false)); - static const VerificationMeta _isEditedMeta = - const VerificationMeta('isEdited'); + static const VerificationMeta _openedAtMeta = + const VerificationMeta('openedAt'); @override - late final GeneratedColumn isEdited = GeneratedColumn( - 'is_edited', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("is_edited" IN (0, 1))'), - defaultValue: const Constant(false)); + late final GeneratedColumn openedAt = GeneratedColumn( + 'opened_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override @@ -2327,6 +2293,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { type: DriftSqlType.dateTime, requiredDuringInsert: false, defaultValue: currentDateAndTime); + static const VerificationMeta _modifiedAtMeta = + const VerificationMeta('modifiedAt'); + @override + late final GeneratedColumn modifiedAt = GeneratedColumn( + 'modified_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); @override List get $columns => [ groupId, @@ -2339,8 +2311,9 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { downloadToken, quotesMessageId, isDeletedFromSender, - isEdited, - createdAt + openedAt, + createdAt, + modifiedAt ]; @override String get aliasedName => _alias ?? actualTableName; @@ -2398,14 +2371,20 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { isDeletedFromSender.isAcceptableOrUnknown( data['is_deleted_from_sender']!, _isDeletedFromSenderMeta)); } - if (data.containsKey('is_edited')) { - context.handle(_isEditedMeta, - isEdited.isAcceptableOrUnknown(data['is_edited']!, _isEditedMeta)); + if (data.containsKey('opened_at')) { + context.handle(_openedAtMeta, + openedAt.isAcceptableOrUnknown(data['opened_at']!, _openedAtMeta)); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } + if (data.containsKey('modified_at')) { + context.handle( + _modifiedAtMeta, + modifiedAt.isAcceptableOrUnknown( + data['modified_at']!, _modifiedAtMeta)); + } return context; } @@ -2435,10 +2414,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { DriftSqlType.string, data['${effectivePrefix}quotes_message_id']), isDeletedFromSender: attachedDatabase.typeMapping.read( DriftSqlType.bool, data['${effectivePrefix}is_deleted_from_sender'])!, - isEdited: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}is_edited'])!, + openedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}opened_at']), createdAt: attachedDatabase.typeMapping .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + modifiedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}modified_at']), ); } @@ -2462,8 +2443,9 @@ class Message extends DataClass implements Insertable { final Uint8List? downloadToken; final String? quotesMessageId; final bool isDeletedFromSender; - final bool isEdited; + final DateTime? openedAt; final DateTime createdAt; + final DateTime? modifiedAt; const Message( {required this.groupId, required this.messageId, @@ -2475,8 +2457,9 @@ class Message extends DataClass implements Insertable { this.downloadToken, this.quotesMessageId, required this.isDeletedFromSender, - required this.isEdited, - required this.createdAt}); + this.openedAt, + required this.createdAt, + this.modifiedAt}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -2502,8 +2485,13 @@ class Message extends DataClass implements Insertable { map['quotes_message_id'] = Variable(quotesMessageId); } map['is_deleted_from_sender'] = Variable(isDeletedFromSender); - map['is_edited'] = Variable(isEdited); + if (!nullToAbsent || openedAt != null) { + map['opened_at'] = Variable(openedAt); + } map['created_at'] = Variable(createdAt); + if (!nullToAbsent || modifiedAt != null) { + map['modified_at'] = Variable(modifiedAt); + } return map; } @@ -2529,8 +2517,13 @@ class Message extends DataClass implements Insertable { ? const Value.absent() : Value(quotesMessageId), isDeletedFromSender: Value(isDeletedFromSender), - isEdited: Value(isEdited), + openedAt: openedAt == null && nullToAbsent + ? const Value.absent() + : Value(openedAt), createdAt: Value(createdAt), + modifiedAt: modifiedAt == null && nullToAbsent + ? const Value.absent() + : Value(modifiedAt), ); } @@ -2550,8 +2543,9 @@ class Message extends DataClass implements Insertable { quotesMessageId: serializer.fromJson(json['quotesMessageId']), isDeletedFromSender: serializer.fromJson(json['isDeletedFromSender']), - isEdited: serializer.fromJson(json['isEdited']), + openedAt: serializer.fromJson(json['openedAt']), createdAt: serializer.fromJson(json['createdAt']), + modifiedAt: serializer.fromJson(json['modifiedAt']), ); } @override @@ -2569,8 +2563,9 @@ class Message extends DataClass implements Insertable { 'downloadToken': serializer.toJson(downloadToken), 'quotesMessageId': serializer.toJson(quotesMessageId), 'isDeletedFromSender': serializer.toJson(isDeletedFromSender), - 'isEdited': serializer.toJson(isEdited), + 'openedAt': serializer.toJson(openedAt), 'createdAt': serializer.toJson(createdAt), + 'modifiedAt': serializer.toJson(modifiedAt), }; } @@ -2585,8 +2580,9 @@ class Message extends DataClass implements Insertable { Value downloadToken = const Value.absent(), Value quotesMessageId = const Value.absent(), bool? isDeletedFromSender, - bool? isEdited, - DateTime? createdAt}) => + Value openedAt = const Value.absent(), + DateTime? createdAt, + Value modifiedAt = const Value.absent()}) => Message( groupId: groupId ?? this.groupId, messageId: messageId ?? this.messageId, @@ -2601,8 +2597,9 @@ class Message extends DataClass implements Insertable { ? quotesMessageId.value : this.quotesMessageId, isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender, - isEdited: isEdited ?? this.isEdited, + openedAt: openedAt.present ? openedAt.value : this.openedAt, createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt.present ? modifiedAt.value : this.modifiedAt, ); Message copyWithCompanion(MessagesCompanion data) { return Message( @@ -2623,8 +2620,10 @@ class Message extends DataClass implements Insertable { isDeletedFromSender: data.isDeletedFromSender.present ? data.isDeletedFromSender.value : this.isDeletedFromSender, - isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + openedAt: data.openedAt.present ? data.openedAt.value : this.openedAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + modifiedAt: + data.modifiedAt.present ? data.modifiedAt.value : this.modifiedAt, ); } @@ -2641,8 +2640,9 @@ class Message extends DataClass implements Insertable { ..write('downloadToken: $downloadToken, ') ..write('quotesMessageId: $quotesMessageId, ') ..write('isDeletedFromSender: $isDeletedFromSender, ') - ..write('isEdited: $isEdited, ') - ..write('createdAt: $createdAt') + ..write('openedAt: $openedAt, ') + ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt') ..write(')')) .toString(); } @@ -2659,8 +2659,9 @@ class Message extends DataClass implements Insertable { $driftBlobEquality.hash(downloadToken), quotesMessageId, isDeletedFromSender, - isEdited, - createdAt); + openedAt, + createdAt, + modifiedAt); @override bool operator ==(Object other) => identical(this, other) || @@ -2675,8 +2676,9 @@ class Message extends DataClass implements Insertable { $driftBlobEquality.equals(other.downloadToken, this.downloadToken) && other.quotesMessageId == this.quotesMessageId && other.isDeletedFromSender == this.isDeletedFromSender && - other.isEdited == this.isEdited && - other.createdAt == this.createdAt); + other.openedAt == this.openedAt && + other.createdAt == this.createdAt && + other.modifiedAt == this.modifiedAt); } class MessagesCompanion extends UpdateCompanion { @@ -2690,8 +2692,9 @@ class MessagesCompanion extends UpdateCompanion { final Value downloadToken; final Value quotesMessageId; final Value isDeletedFromSender; - final Value isEdited; + final Value openedAt; final Value createdAt; + final Value modifiedAt; final Value rowid; const MessagesCompanion({ this.groupId = const Value.absent(), @@ -2704,8 +2707,9 @@ class MessagesCompanion extends UpdateCompanion { this.downloadToken = const Value.absent(), this.quotesMessageId = const Value.absent(), this.isDeletedFromSender = const Value.absent(), - this.isEdited = const Value.absent(), + this.openedAt = const Value.absent(), this.createdAt = const Value.absent(), + this.modifiedAt = const Value.absent(), this.rowid = const Value.absent(), }); MessagesCompanion.insert({ @@ -2719,8 +2723,9 @@ class MessagesCompanion extends UpdateCompanion { this.downloadToken = const Value.absent(), this.quotesMessageId = const Value.absent(), this.isDeletedFromSender = const Value.absent(), - this.isEdited = const Value.absent(), + this.openedAt = const Value.absent(), this.createdAt = const Value.absent(), + this.modifiedAt = const Value.absent(), this.rowid = const Value.absent(), }) : groupId = Value(groupId), type = Value(type); @@ -2735,8 +2740,9 @@ class MessagesCompanion extends UpdateCompanion { Expression? downloadToken, Expression? quotesMessageId, Expression? isDeletedFromSender, - Expression? isEdited, + Expression? openedAt, Expression? createdAt, + Expression? modifiedAt, Expression? rowid, }) { return RawValuesInsertable({ @@ -2751,8 +2757,9 @@ class MessagesCompanion extends UpdateCompanion { if (quotesMessageId != null) 'quotes_message_id': quotesMessageId, if (isDeletedFromSender != null) 'is_deleted_from_sender': isDeletedFromSender, - if (isEdited != null) 'is_edited': isEdited, + if (openedAt != null) 'opened_at': openedAt, if (createdAt != null) 'created_at': createdAt, + if (modifiedAt != null) 'modified_at': modifiedAt, if (rowid != null) 'rowid': rowid, }); } @@ -2768,8 +2775,9 @@ class MessagesCompanion extends UpdateCompanion { Value? downloadToken, Value? quotesMessageId, Value? isDeletedFromSender, - Value? isEdited, + Value? openedAt, Value? createdAt, + Value? modifiedAt, Value? rowid}) { return MessagesCompanion( groupId: groupId ?? this.groupId, @@ -2782,8 +2790,9 @@ class MessagesCompanion extends UpdateCompanion { downloadToken: downloadToken ?? this.downloadToken, quotesMessageId: quotesMessageId ?? this.quotesMessageId, isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender, - isEdited: isEdited ?? this.isEdited, + openedAt: openedAt ?? this.openedAt, createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt ?? this.modifiedAt, rowid: rowid ?? this.rowid, ); } @@ -2822,12 +2831,15 @@ class MessagesCompanion extends UpdateCompanion { if (isDeletedFromSender.present) { map['is_deleted_from_sender'] = Variable(isDeletedFromSender.value); } - if (isEdited.present) { - map['is_edited'] = Variable(isEdited.value); + if (openedAt.present) { + map['opened_at'] = Variable(openedAt.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); } + if (modifiedAt.present) { + map['modified_at'] = Variable(modifiedAt.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -2847,8 +2859,9 @@ class MessagesCompanion extends UpdateCompanion { ..write('downloadToken: $downloadToken, ') ..write('quotesMessageId: $quotesMessageId, ') ..write('isDeletedFromSender: $isDeletedFromSender, ') - ..write('isEdited: $isEdited, ') + ..write('openedAt: $openedAt, ') ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -2883,8 +2896,8 @@ class $MessageHistoriesTable extends MessageHistories const VerificationMeta('contactId'); @override late final GeneratedColumn contactId = GeneratedColumn( - 'contact_id', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); + 'contact_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); static const VerificationMeta _contentMeta = const VerificationMeta('content'); @override @@ -2924,8 +2937,6 @@ class $MessageHistoriesTable extends MessageHistories if (data.containsKey('contact_id')) { context.handle(_contactIdMeta, contactId.isAcceptableOrUnknown(data['contact_id']!, _contactIdMeta)); - } else if (isInserting) { - context.missing(_contactIdMeta); } if (data.containsKey('content')) { context.handle(_contentMeta, @@ -2949,7 +2960,7 @@ class $MessageHistoriesTable extends MessageHistories messageId: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}message_id'])!, contactId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}contact_id'])!, + .read(DriftSqlType.int, data['${effectivePrefix}contact_id']), content: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}content']), createdAt: attachedDatabase.typeMapping @@ -2966,13 +2977,13 @@ class $MessageHistoriesTable extends MessageHistories class MessageHistory extends DataClass implements Insertable { final int id; final String messageId; - final int contactId; + final int? contactId; final String? content; final DateTime createdAt; const MessageHistory( {required this.id, required this.messageId, - required this.contactId, + this.contactId, this.content, required this.createdAt}); @override @@ -2980,7 +2991,9 @@ class MessageHistory extends DataClass implements Insertable { final map = {}; map['id'] = Variable(id); map['message_id'] = Variable(messageId); - map['contact_id'] = Variable(contactId); + if (!nullToAbsent || contactId != null) { + map['contact_id'] = Variable(contactId); + } if (!nullToAbsent || content != null) { map['content'] = Variable(content); } @@ -2992,7 +3005,9 @@ class MessageHistory extends DataClass implements Insertable { return MessageHistoriesCompanion( id: Value(id), messageId: Value(messageId), - contactId: Value(contactId), + contactId: contactId == null && nullToAbsent + ? const Value.absent() + : Value(contactId), content: content == null && nullToAbsent ? const Value.absent() : Value(content), @@ -3006,7 +3021,7 @@ class MessageHistory extends DataClass implements Insertable { return MessageHistory( id: serializer.fromJson(json['id']), messageId: serializer.fromJson(json['messageId']), - contactId: serializer.fromJson(json['contactId']), + contactId: serializer.fromJson(json['contactId']), content: serializer.fromJson(json['content']), createdAt: serializer.fromJson(json['createdAt']), ); @@ -3017,7 +3032,7 @@ class MessageHistory extends DataClass implements Insertable { return { 'id': serializer.toJson(id), 'messageId': serializer.toJson(messageId), - 'contactId': serializer.toJson(contactId), + 'contactId': serializer.toJson(contactId), 'content': serializer.toJson(content), 'createdAt': serializer.toJson(createdAt), }; @@ -3026,13 +3041,13 @@ class MessageHistory extends DataClass implements Insertable { MessageHistory copyWith( {int? id, String? messageId, - int? contactId, + Value contactId = const Value.absent(), Value content = const Value.absent(), DateTime? createdAt}) => MessageHistory( id: id ?? this.id, messageId: messageId ?? this.messageId, - contactId: contactId ?? this.contactId, + contactId: contactId.present ? contactId.value : this.contactId, content: content.present ? content.value : this.content, createdAt: createdAt ?? this.createdAt, ); @@ -3074,7 +3089,7 @@ class MessageHistory extends DataClass implements Insertable { class MessageHistoriesCompanion extends UpdateCompanion { final Value id; final Value messageId; - final Value contactId; + final Value contactId; final Value content; final Value createdAt; const MessageHistoriesCompanion({ @@ -3087,11 +3102,10 @@ class MessageHistoriesCompanion extends UpdateCompanion { MessageHistoriesCompanion.insert({ this.id = const Value.absent(), required String messageId, - required int contactId, + this.contactId = const Value.absent(), this.content = const Value.absent(), this.createdAt = const Value.absent(), - }) : messageId = Value(messageId), - contactId = Value(contactId); + }) : messageId = Value(messageId); static Insertable custom({ Expression? id, Expression? messageId, @@ -3111,7 +3125,7 @@ class MessageHistoriesCompanion extends UpdateCompanion { MessageHistoriesCompanion copyWith( {Value? id, Value? messageId, - Value? contactId, + Value? contactId, Value? content, Value? createdAt}) { return MessageHistoriesCompanion( @@ -5827,15 +5841,6 @@ class $MessageActionsTable extends MessageActions final GeneratedDatabase attachedDatabase; final String? _alias; $MessageActionsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); static const VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); @override @@ -5866,8 +5871,7 @@ class $MessageActionsTable extends MessageActions requiredDuringInsert: false, defaultValue: currentDateAndTime); @override - List get $columns => - [id, messageId, contactId, type, actionAt]; + List get $columns => [messageId, contactId, type, actionAt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -5878,9 +5882,6 @@ class $MessageActionsTable extends MessageActions {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); - } if (data.containsKey('message_id')) { context.handle(_messageIdMeta, messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); @@ -5901,13 +5902,11 @@ class $MessageActionsTable extends MessageActions } @override - Set get $primaryKey => {id}; + Set get $primaryKey => {messageId, contactId, type}; @override MessageAction map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return MessageAction( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, messageId: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}message_id'])!, contactId: attachedDatabase.typeMapping @@ -5930,21 +5929,18 @@ class $MessageActionsTable extends MessageActions } class MessageAction extends DataClass implements Insertable { - final int id; final String messageId; final int contactId; final MessageActionType type; final DateTime actionAt; const MessageAction( - {required this.id, - required this.messageId, + {required this.messageId, required this.contactId, required this.type, required this.actionAt}); @override Map toColumns(bool nullToAbsent) { final map = {}; - map['id'] = Variable(id); map['message_id'] = Variable(messageId); map['contact_id'] = Variable(contactId); { @@ -5957,7 +5953,6 @@ class MessageAction extends DataClass implements Insertable { MessageActionsCompanion toCompanion(bool nullToAbsent) { return MessageActionsCompanion( - id: Value(id), messageId: Value(messageId), contactId: Value(contactId), type: Value(type), @@ -5969,7 +5964,6 @@ class MessageAction extends DataClass implements Insertable { {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return MessageAction( - id: serializer.fromJson(json['id']), messageId: serializer.fromJson(json['messageId']), contactId: serializer.fromJson(json['contactId']), type: $MessageActionsTable.$convertertype @@ -5981,7 +5975,6 @@ class MessageAction extends DataClass implements Insertable { Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'id': serializer.toJson(id), 'messageId': serializer.toJson(messageId), 'contactId': serializer.toJson(contactId), 'type': serializer @@ -5991,13 +5984,11 @@ class MessageAction extends DataClass implements Insertable { } MessageAction copyWith( - {int? id, - String? messageId, + {String? messageId, int? contactId, MessageActionType? type, DateTime? actionAt}) => MessageAction( - id: id ?? this.id, messageId: messageId ?? this.messageId, contactId: contactId ?? this.contactId, type: type ?? this.type, @@ -6005,7 +5996,6 @@ class MessageAction extends DataClass implements Insertable { ); MessageAction copyWithCompanion(MessageActionsCompanion data) { return MessageAction( - id: data.id.present ? data.id.value : this.id, messageId: data.messageId.present ? data.messageId.value : this.messageId, contactId: data.contactId.present ? data.contactId.value : this.contactId, type: data.type.present ? data.type.value : this.type, @@ -6016,7 +6006,6 @@ class MessageAction extends DataClass implements Insertable { @override String toString() { return (StringBuffer('MessageAction(') - ..write('id: $id, ') ..write('messageId: $messageId, ') ..write('contactId: $contactId, ') ..write('type: $type, ') @@ -6026,12 +6015,11 @@ class MessageAction extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(id, messageId, contactId, type, actionAt); + int get hashCode => Object.hash(messageId, contactId, type, actionAt); @override bool operator ==(Object other) => identical(this, other) || (other is MessageAction && - other.id == this.id && other.messageId == this.messageId && other.contactId == this.contactId && other.type == this.type && @@ -6039,64 +6027,61 @@ class MessageAction extends DataClass implements Insertable { } class MessageActionsCompanion extends UpdateCompanion { - final Value id; final Value messageId; final Value contactId; final Value type; final Value actionAt; + final Value rowid; const MessageActionsCompanion({ - this.id = const Value.absent(), this.messageId = const Value.absent(), this.contactId = const Value.absent(), this.type = const Value.absent(), this.actionAt = const Value.absent(), + this.rowid = const Value.absent(), }); MessageActionsCompanion.insert({ - this.id = const Value.absent(), required String messageId, required int contactId, required MessageActionType type, this.actionAt = const Value.absent(), + this.rowid = const Value.absent(), }) : messageId = Value(messageId), contactId = Value(contactId), type = Value(type); static Insertable custom({ - Expression? id, Expression? messageId, Expression? contactId, Expression? type, Expression? actionAt, + Expression? rowid, }) { return RawValuesInsertable({ - if (id != null) 'id': id, if (messageId != null) 'message_id': messageId, if (contactId != null) 'contact_id': contactId, if (type != null) 'type': type, if (actionAt != null) 'action_at': actionAt, + if (rowid != null) 'rowid': rowid, }); } MessageActionsCompanion copyWith( - {Value? id, - Value? messageId, + {Value? messageId, Value? contactId, Value? type, - Value? actionAt}) { + Value? actionAt, + Value? rowid}) { return MessageActionsCompanion( - id: id ?? this.id, messageId: messageId ?? this.messageId, contactId: contactId ?? this.contactId, type: type ?? this.type, actionAt: actionAt ?? this.actionAt, + rowid: rowid ?? this.rowid, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } if (messageId.present) { map['message_id'] = Variable(messageId.value); } @@ -6110,17 +6095,20 @@ class MessageActionsCompanion extends UpdateCompanion { if (actionAt.present) { map['action_at'] = Variable(actionAt.value); } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } return map; } @override String toString() { return (StringBuffer('MessageActionsCompanion(') - ..write('id: $id, ') ..write('messageId: $messageId, ') ..write('contactId: $contactId, ') ..write('type: $type, ') - ..write('actionAt: $actionAt') + ..write('actionAt: $actionAt, ') + ..write('rowid: $rowid') ..write(')')) .toString(); } @@ -6266,19 +6254,10 @@ typedef $$ContactsTableCreateCompanionBuilder = ContactsCompanion Function({ Value senderProfileCounter, Value accepted, Value requested, - Value hidden, Value blocked, Value verified, Value deleted, - Value alsoBestFriend, - Value deleteMessagesAfterXMinutes, Value createdAt, - Value totalMediaCounter, - Value lastMessageSend, - Value lastMessageReceived, - Value lastFlameCounterChange, - Value lastFlameSync, - Value flameCounter, }); typedef $$ContactsTableUpdateCompanionBuilder = ContactsCompanion Function({ Value userId, @@ -6289,19 +6268,10 @@ typedef $$ContactsTableUpdateCompanionBuilder = ContactsCompanion Function({ Value senderProfileCounter, Value accepted, Value requested, - Value hidden, Value blocked, Value verified, Value deleted, - Value alsoBestFriend, - Value deleteMessagesAfterXMinutes, Value createdAt, - Value totalMediaCounter, - Value lastMessageSend, - Value lastMessageReceived, - Value lastFlameCounterChange, - Value lastFlameSync, - Value flameCounter, }); final class $$ContactsTableReferences @@ -6444,9 +6414,6 @@ class $$ContactsTableFilterComposer ColumnFilters get requested => $composableBuilder( column: $table.requested, builder: (column) => ColumnFilters(column)); - ColumnFilters get hidden => $composableBuilder( - column: $table.hidden, builder: (column) => ColumnFilters(column)); - ColumnFilters get blocked => $composableBuilder( column: $table.blocked, builder: (column) => ColumnFilters(column)); @@ -6456,39 +6423,9 @@ class $$ContactsTableFilterComposer ColumnFilters get deleted => $composableBuilder( column: $table.deleted, builder: (column) => ColumnFilters(column)); - ColumnFilters get alsoBestFriend => $composableBuilder( - column: $table.alsoBestFriend, - builder: (column) => ColumnFilters(column)); - - ColumnFilters get deleteMessagesAfterXMinutes => $composableBuilder( - column: $table.deleteMessagesAfterXMinutes, - builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get totalMediaCounter => $composableBuilder( - column: $table.totalMediaCounter, - builder: (column) => ColumnFilters(column)); - - ColumnFilters get lastMessageSend => $composableBuilder( - column: $table.lastMessageSend, - builder: (column) => ColumnFilters(column)); - - ColumnFilters get lastMessageReceived => $composableBuilder( - column: $table.lastMessageReceived, - builder: (column) => ColumnFilters(column)); - - ColumnFilters get lastFlameCounterChange => $composableBuilder( - column: $table.lastFlameCounterChange, - builder: (column) => ColumnFilters(column)); - - ColumnFilters get lastFlameSync => $composableBuilder( - column: $table.lastFlameSync, builder: (column) => ColumnFilters(column)); - - ColumnFilters get flameCounter => $composableBuilder( - column: $table.flameCounter, builder: (column) => ColumnFilters(column)); - Expression messagesRefs( Expression Function($$MessagesTableFilterComposer f) f) { final $$MessagesTableFilterComposer composer = $composerBuilder( @@ -6654,9 +6591,6 @@ class $$ContactsTableOrderingComposer ColumnOrderings get requested => $composableBuilder( column: $table.requested, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get hidden => $composableBuilder( - column: $table.hidden, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get blocked => $composableBuilder( column: $table.blocked, builder: (column) => ColumnOrderings(column)); @@ -6666,40 +6600,8 @@ class $$ContactsTableOrderingComposer ColumnOrderings get deleted => $composableBuilder( column: $table.deleted, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get alsoBestFriend => $composableBuilder( - column: $table.alsoBestFriend, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get deleteMessagesAfterXMinutes => $composableBuilder( - column: $table.deleteMessagesAfterXMinutes, - builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get totalMediaCounter => $composableBuilder( - column: $table.totalMediaCounter, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get lastMessageSend => $composableBuilder( - column: $table.lastMessageSend, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get lastMessageReceived => $composableBuilder( - column: $table.lastMessageReceived, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get lastFlameCounterChange => $composableBuilder( - column: $table.lastFlameCounterChange, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get lastFlameSync => $composableBuilder( - column: $table.lastFlameSync, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get flameCounter => $composableBuilder( - column: $table.flameCounter, - builder: (column) => ColumnOrderings(column)); } class $$ContactsTableAnnotationComposer @@ -6735,9 +6637,6 @@ class $$ContactsTableAnnotationComposer GeneratedColumn get requested => $composableBuilder(column: $table.requested, builder: (column) => column); - GeneratedColumn get hidden => - $composableBuilder(column: $table.hidden, builder: (column) => column); - GeneratedColumn get blocked => $composableBuilder(column: $table.blocked, builder: (column) => column); @@ -6747,33 +6646,9 @@ class $$ContactsTableAnnotationComposer GeneratedColumn get deleted => $composableBuilder(column: $table.deleted, builder: (column) => column); - GeneratedColumn get alsoBestFriend => $composableBuilder( - column: $table.alsoBestFriend, builder: (column) => column); - - GeneratedColumn get deleteMessagesAfterXMinutes => $composableBuilder( - column: $table.deleteMessagesAfterXMinutes, builder: (column) => column); - GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get totalMediaCounter => $composableBuilder( - column: $table.totalMediaCounter, builder: (column) => column); - - GeneratedColumn get lastMessageSend => $composableBuilder( - column: $table.lastMessageSend, builder: (column) => column); - - GeneratedColumn get lastMessageReceived => $composableBuilder( - column: $table.lastMessageReceived, builder: (column) => column); - - GeneratedColumn get lastFlameCounterChange => $composableBuilder( - column: $table.lastFlameCounterChange, builder: (column) => column); - - GeneratedColumn get lastFlameSync => $composableBuilder( - column: $table.lastFlameSync, builder: (column) => column); - - GeneratedColumn get flameCounter => $composableBuilder( - column: $table.flameCounter, builder: (column) => column); - Expression messagesRefs( Expression Function($$MessagesTableAnnotationComposer a) f) { final $$MessagesTableAnnotationComposer composer = $composerBuilder( @@ -6943,19 +6818,10 @@ class $$ContactsTableTableManager extends RootTableManager< Value senderProfileCounter = const Value.absent(), Value accepted = const Value.absent(), Value requested = const Value.absent(), - Value hidden = const Value.absent(), Value blocked = const Value.absent(), Value verified = const Value.absent(), Value deleted = const Value.absent(), - Value alsoBestFriend = const Value.absent(), - Value deleteMessagesAfterXMinutes = const Value.absent(), Value createdAt = const Value.absent(), - Value totalMediaCounter = const Value.absent(), - Value lastMessageSend = const Value.absent(), - Value lastMessageReceived = const Value.absent(), - Value lastFlameCounterChange = const Value.absent(), - Value lastFlameSync = const Value.absent(), - Value flameCounter = const Value.absent(), }) => ContactsCompanion( userId: userId, @@ -6966,19 +6832,10 @@ class $$ContactsTableTableManager extends RootTableManager< senderProfileCounter: senderProfileCounter, accepted: accepted, requested: requested, - hidden: hidden, blocked: blocked, verified: verified, deleted: deleted, - alsoBestFriend: alsoBestFriend, - deleteMessagesAfterXMinutes: deleteMessagesAfterXMinutes, createdAt: createdAt, - totalMediaCounter: totalMediaCounter, - lastMessageSend: lastMessageSend, - lastMessageReceived: lastMessageReceived, - lastFlameCounterChange: lastFlameCounterChange, - lastFlameSync: lastFlameSync, - flameCounter: flameCounter, ), createCompanionCallback: ({ Value userId = const Value.absent(), @@ -6989,19 +6846,10 @@ class $$ContactsTableTableManager extends RootTableManager< Value senderProfileCounter = const Value.absent(), Value accepted = const Value.absent(), Value requested = const Value.absent(), - Value hidden = const Value.absent(), Value blocked = const Value.absent(), Value verified = const Value.absent(), Value deleted = const Value.absent(), - Value alsoBestFriend = const Value.absent(), - Value deleteMessagesAfterXMinutes = const Value.absent(), Value createdAt = const Value.absent(), - Value totalMediaCounter = const Value.absent(), - Value lastMessageSend = const Value.absent(), - Value lastMessageReceived = const Value.absent(), - Value lastFlameCounterChange = const Value.absent(), - Value lastFlameSync = const Value.absent(), - Value flameCounter = const Value.absent(), }) => ContactsCompanion.insert( userId: userId, @@ -7012,19 +6860,10 @@ class $$ContactsTableTableManager extends RootTableManager< senderProfileCounter: senderProfileCounter, accepted: accepted, requested: requested, - hidden: hidden, blocked: blocked, verified: verified, deleted: deleted, - alsoBestFriend: alsoBestFriend, - deleteMessagesAfterXMinutes: deleteMessagesAfterXMinutes, createdAt: createdAt, - totalMediaCounter: totalMediaCounter, - lastMessageSend: lastMessageSend, - lastMessageReceived: lastMessageReceived, - lastFlameCounterChange: lastFlameCounterChange, - lastFlameSync: lastFlameSync, - flameCounter: flameCounter, ), withReferenceMapper: (p0) => p0 .map((e) => @@ -7155,23 +6994,39 @@ typedef $$ContactsTableProcessedTableManager = ProcessedTableManager< typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({ Value groupId, required bool isGroupAdmin, - required bool isGroupOfTwo, + required bool isDirectChat, Value pinned, Value archived, required String groupName, - Value lastMessageExchange, + Value totalMediaCounter, + Value alsoBestFriend, + Value deleteMessagesAfterMilliseconds, Value createdAt, + Value lastMessageSend, + Value lastMessageReceived, + Value lastFlameCounterChange, + Value lastFlameSync, + Value flameCounter, + Value lastMessageExchange, Value rowid, }); typedef $$GroupsTableUpdateCompanionBuilder = GroupsCompanion Function({ Value groupId, Value isGroupAdmin, - Value isGroupOfTwo, + Value isDirectChat, Value pinned, Value archived, Value groupName, - Value lastMessageExchange, + Value totalMediaCounter, + Value alsoBestFriend, + Value deleteMessagesAfterMilliseconds, Value createdAt, + Value lastMessageSend, + Value lastMessageReceived, + Value lastFlameCounterChange, + Value lastFlameSync, + Value flameCounter, + Value lastMessageExchange, Value rowid, }); @@ -7209,8 +7064,8 @@ class $$GroupsTableFilterComposer extends Composer<_$TwonlyDB, $GroupsTable> { ColumnFilters get isGroupAdmin => $composableBuilder( column: $table.isGroupAdmin, builder: (column) => ColumnFilters(column)); - ColumnFilters get isGroupOfTwo => $composableBuilder( - column: $table.isGroupOfTwo, builder: (column) => ColumnFilters(column)); + ColumnFilters get isDirectChat => $composableBuilder( + column: $table.isDirectChat, builder: (column) => ColumnFilters(column)); ColumnFilters get pinned => $composableBuilder( column: $table.pinned, builder: (column) => ColumnFilters(column)); @@ -7221,13 +7076,43 @@ class $$GroupsTableFilterComposer extends Composer<_$TwonlyDB, $GroupsTable> { ColumnFilters get groupName => $composableBuilder( column: $table.groupName, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastMessageExchange => $composableBuilder( - column: $table.lastMessageExchange, + ColumnFilters get totalMediaCounter => $composableBuilder( + column: $table.totalMediaCounter, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get alsoBestFriend => $composableBuilder( + column: $table.alsoBestFriend, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get deleteMessagesAfterMilliseconds => $composableBuilder( + column: $table.deleteMessagesAfterMilliseconds, builder: (column) => ColumnFilters(column)); ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastMessageSend => $composableBuilder( + column: $table.lastMessageSend, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get lastMessageReceived => $composableBuilder( + column: $table.lastMessageReceived, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get lastFlameCounterChange => $composableBuilder( + column: $table.lastFlameCounterChange, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get lastFlameSync => $composableBuilder( + column: $table.lastFlameSync, builder: (column) => ColumnFilters(column)); + + ColumnFilters get flameCounter => $composableBuilder( + column: $table.flameCounter, builder: (column) => ColumnFilters(column)); + + ColumnFilters get lastMessageExchange => $composableBuilder( + column: $table.lastMessageExchange, + builder: (column) => ColumnFilters(column)); + Expression messagesRefs( Expression Function($$MessagesTableFilterComposer f) f) { final $$MessagesTableFilterComposer composer = $composerBuilder( @@ -7265,8 +7150,8 @@ class $$GroupsTableOrderingComposer extends Composer<_$TwonlyDB, $GroupsTable> { column: $table.isGroupAdmin, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get isGroupOfTwo => $composableBuilder( - column: $table.isGroupOfTwo, + ColumnOrderings get isDirectChat => $composableBuilder( + column: $table.isDirectChat, builder: (column) => ColumnOrderings(column)); ColumnOrderings get pinned => $composableBuilder( @@ -7278,12 +7163,45 @@ class $$GroupsTableOrderingComposer extends Composer<_$TwonlyDB, $GroupsTable> { ColumnOrderings get groupName => $composableBuilder( column: $table.groupName, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastMessageExchange => $composableBuilder( - column: $table.lastMessageExchange, + ColumnOrderings get totalMediaCounter => $composableBuilder( + column: $table.totalMediaCounter, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get alsoBestFriend => $composableBuilder( + column: $table.alsoBestFriend, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get deleteMessagesAfterMilliseconds => + $composableBuilder( + column: $table.deleteMessagesAfterMilliseconds, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastMessageSend => $composableBuilder( + column: $table.lastMessageSend, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastMessageReceived => $composableBuilder( + column: $table.lastMessageReceived, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastFlameCounterChange => $composableBuilder( + column: $table.lastFlameCounterChange, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastFlameSync => $composableBuilder( + column: $table.lastFlameSync, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get flameCounter => $composableBuilder( + column: $table.flameCounter, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastMessageExchange => $composableBuilder( + column: $table.lastMessageExchange, + builder: (column) => ColumnOrderings(column)); } class $$GroupsTableAnnotationComposer @@ -7301,8 +7219,8 @@ class $$GroupsTableAnnotationComposer GeneratedColumn get isGroupAdmin => $composableBuilder( column: $table.isGroupAdmin, builder: (column) => column); - GeneratedColumn get isGroupOfTwo => $composableBuilder( - column: $table.isGroupOfTwo, builder: (column) => column); + GeneratedColumn get isDirectChat => $composableBuilder( + column: $table.isDirectChat, builder: (column) => column); GeneratedColumn get pinned => $composableBuilder(column: $table.pinned, builder: (column) => column); @@ -7313,12 +7231,38 @@ class $$GroupsTableAnnotationComposer GeneratedColumn get groupName => $composableBuilder(column: $table.groupName, builder: (column) => column); - GeneratedColumn get lastMessageExchange => $composableBuilder( - column: $table.lastMessageExchange, builder: (column) => column); + GeneratedColumn get totalMediaCounter => $composableBuilder( + column: $table.totalMediaCounter, builder: (column) => column); + + GeneratedColumn get alsoBestFriend => $composableBuilder( + column: $table.alsoBestFriend, builder: (column) => column); + + GeneratedColumn get deleteMessagesAfterMilliseconds => + $composableBuilder( + column: $table.deleteMessagesAfterMilliseconds, + builder: (column) => column); GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get lastMessageSend => $composableBuilder( + column: $table.lastMessageSend, builder: (column) => column); + + GeneratedColumn get lastMessageReceived => $composableBuilder( + column: $table.lastMessageReceived, builder: (column) => column); + + GeneratedColumn get lastFlameCounterChange => $composableBuilder( + column: $table.lastFlameCounterChange, builder: (column) => column); + + GeneratedColumn get lastFlameSync => $composableBuilder( + column: $table.lastFlameSync, builder: (column) => column); + + GeneratedColumn get flameCounter => $composableBuilder( + column: $table.flameCounter, builder: (column) => column); + + GeneratedColumn get lastMessageExchange => $composableBuilder( + column: $table.lastMessageExchange, builder: (column) => column); + Expression messagesRefs( Expression Function($$MessagesTableAnnotationComposer a) f) { final $$MessagesTableAnnotationComposer composer = $composerBuilder( @@ -7366,45 +7310,77 @@ class $$GroupsTableTableManager extends RootTableManager< updateCompanionCallback: ({ Value groupId = const Value.absent(), Value isGroupAdmin = const Value.absent(), - Value isGroupOfTwo = const Value.absent(), + Value isDirectChat = const Value.absent(), Value pinned = const Value.absent(), Value archived = const Value.absent(), Value groupName = const Value.absent(), - Value lastMessageExchange = const Value.absent(), + Value totalMediaCounter = const Value.absent(), + Value alsoBestFriend = const Value.absent(), + Value deleteMessagesAfterMilliseconds = const Value.absent(), Value createdAt = const Value.absent(), + Value lastMessageSend = const Value.absent(), + Value lastMessageReceived = const Value.absent(), + Value lastFlameCounterChange = const Value.absent(), + Value lastFlameSync = const Value.absent(), + Value flameCounter = const Value.absent(), + Value lastMessageExchange = const Value.absent(), Value rowid = const Value.absent(), }) => GroupsCompanion( groupId: groupId, isGroupAdmin: isGroupAdmin, - isGroupOfTwo: isGroupOfTwo, + isDirectChat: isDirectChat, pinned: pinned, archived: archived, groupName: groupName, - lastMessageExchange: lastMessageExchange, + totalMediaCounter: totalMediaCounter, + alsoBestFriend: alsoBestFriend, + deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds, createdAt: createdAt, + lastMessageSend: lastMessageSend, + lastMessageReceived: lastMessageReceived, + lastFlameCounterChange: lastFlameCounterChange, + lastFlameSync: lastFlameSync, + flameCounter: flameCounter, + lastMessageExchange: lastMessageExchange, rowid: rowid, ), createCompanionCallback: ({ Value groupId = const Value.absent(), required bool isGroupAdmin, - required bool isGroupOfTwo, + required bool isDirectChat, Value pinned = const Value.absent(), Value archived = const Value.absent(), required String groupName, - Value lastMessageExchange = const Value.absent(), + Value totalMediaCounter = const Value.absent(), + Value alsoBestFriend = const Value.absent(), + Value deleteMessagesAfterMilliseconds = const Value.absent(), Value createdAt = const Value.absent(), + Value lastMessageSend = const Value.absent(), + Value lastMessageReceived = const Value.absent(), + Value lastFlameCounterChange = const Value.absent(), + Value lastFlameSync = const Value.absent(), + Value flameCounter = const Value.absent(), + Value lastMessageExchange = const Value.absent(), Value rowid = const Value.absent(), }) => GroupsCompanion.insert( groupId: groupId, isGroupAdmin: isGroupAdmin, - isGroupOfTwo: isGroupOfTwo, + isDirectChat: isDirectChat, pinned: pinned, archived: archived, groupName: groupName, - lastMessageExchange: lastMessageExchange, + totalMediaCounter: totalMediaCounter, + alsoBestFriend: alsoBestFriend, + deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds, createdAt: createdAt, + lastMessageSend: lastMessageSend, + lastMessageReceived: lastMessageReceived, + lastFlameCounterChange: lastFlameCounterChange, + lastFlameSync: lastFlameSync, + flameCounter: flameCounter, + lastMessageExchange: lastMessageExchange, rowid: rowid, ), withReferenceMapper: (p0) => p0 @@ -7871,8 +7847,9 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({ Value downloadToken, Value quotesMessageId, Value isDeletedFromSender, - Value isEdited, + Value openedAt, Value createdAt, + Value modifiedAt, Value rowid, }); typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ @@ -7886,8 +7863,9 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ Value downloadToken, Value quotesMessageId, Value isDeletedFromSender, - Value isEdited, + Value openedAt, Value createdAt, + Value modifiedAt, Value rowid, }); @@ -8051,12 +8029,15 @@ class $$MessagesTableFilterComposer column: $table.isDeletedFromSender, builder: (column) => ColumnFilters(column)); - ColumnFilters get isEdited => $composableBuilder( - column: $table.isEdited, builder: (column) => ColumnFilters(column)); + ColumnFilters get openedAt => $composableBuilder( + column: $table.openedAt, builder: (column) => ColumnFilters(column)); ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => ColumnFilters(column)); + $$GroupsTableFilterComposer get groupId { final $$GroupsTableFilterComposer composer = $composerBuilder( composer: this, @@ -8251,12 +8232,15 @@ class $$MessagesTableOrderingComposer column: $table.isDeletedFromSender, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get isEdited => $composableBuilder( - column: $table.isEdited, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get openedAt => $composableBuilder( + column: $table.openedAt, builder: (column) => ColumnOrderings(column)); ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => ColumnOrderings(column)); + $$GroupsTableOrderingComposer get groupId { final $$GroupsTableOrderingComposer composer = $composerBuilder( composer: this, @@ -8365,12 +8349,15 @@ class $$MessagesTableAnnotationComposer GeneratedColumn get isDeletedFromSender => $composableBuilder( column: $table.isDeletedFromSender, builder: (column) => column); - GeneratedColumn get isEdited => - $composableBuilder(column: $table.isEdited, builder: (column) => column); + GeneratedColumn get openedAt => + $composableBuilder(column: $table.openedAt, builder: (column) => column); GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => column); + $$GroupsTableAnnotationComposer get groupId { final $$GroupsTableAnnotationComposer composer = $composerBuilder( composer: this, @@ -8577,8 +8564,9 @@ class $$MessagesTableTableManager extends RootTableManager< Value downloadToken = const Value.absent(), Value quotesMessageId = const Value.absent(), Value isDeletedFromSender = const Value.absent(), - Value isEdited = const Value.absent(), + Value openedAt = const Value.absent(), Value createdAt = const Value.absent(), + Value modifiedAt = const Value.absent(), Value rowid = const Value.absent(), }) => MessagesCompanion( @@ -8592,8 +8580,9 @@ class $$MessagesTableTableManager extends RootTableManager< downloadToken: downloadToken, quotesMessageId: quotesMessageId, isDeletedFromSender: isDeletedFromSender, - isEdited: isEdited, + openedAt: openedAt, createdAt: createdAt, + modifiedAt: modifiedAt, rowid: rowid, ), createCompanionCallback: ({ @@ -8607,8 +8596,9 @@ class $$MessagesTableTableManager extends RootTableManager< Value downloadToken = const Value.absent(), Value quotesMessageId = const Value.absent(), Value isDeletedFromSender = const Value.absent(), - Value isEdited = const Value.absent(), + Value openedAt = const Value.absent(), Value createdAt = const Value.absent(), + Value modifiedAt = const Value.absent(), Value rowid = const Value.absent(), }) => MessagesCompanion.insert( @@ -8622,8 +8612,9 @@ class $$MessagesTableTableManager extends RootTableManager< downloadToken: downloadToken, quotesMessageId: quotesMessageId, isDeletedFromSender: isDeletedFromSender, - isEdited: isEdited, + openedAt: openedAt, createdAt: createdAt, + modifiedAt: modifiedAt, rowid: rowid, ), withReferenceMapper: (p0) => p0 @@ -8788,7 +8779,7 @@ typedef $$MessageHistoriesTableCreateCompanionBuilder = MessageHistoriesCompanion Function({ Value id, required String messageId, - required int contactId, + Value contactId, Value content, Value createdAt, }); @@ -8796,7 +8787,7 @@ typedef $$MessageHistoriesTableUpdateCompanionBuilder = MessageHistoriesCompanion Function({ Value id, Value messageId, - Value contactId, + Value contactId, Value content, Value createdAt, }); @@ -8974,7 +8965,7 @@ class $$MessageHistoriesTableTableManager extends RootTableManager< updateCompanionCallback: ({ Value id = const Value.absent(), Value messageId = const Value.absent(), - Value contactId = const Value.absent(), + Value contactId = const Value.absent(), Value content = const Value.absent(), Value createdAt = const Value.absent(), }) => @@ -8988,7 +8979,7 @@ class $$MessageHistoriesTableTableManager extends RootTableManager< createCompanionCallback: ({ Value id = const Value.absent(), required String messageId, - required int contactId, + Value contactId = const Value.absent(), Value content = const Value.absent(), Value createdAt = const Value.absent(), }) => @@ -11220,19 +11211,19 @@ typedef $$SignalContactSignedPreKeysTableProcessedTableManager PrefetchHooks Function({bool contactId})>; typedef $$MessageActionsTableCreateCompanionBuilder = MessageActionsCompanion Function({ - Value id, required String messageId, required int contactId, required MessageActionType type, Value actionAt, + Value rowid, }); typedef $$MessageActionsTableUpdateCompanionBuilder = MessageActionsCompanion Function({ - Value id, Value messageId, Value contactId, Value type, Value actionAt, + Value rowid, }); final class $$MessageActionsTableReferences @@ -11265,9 +11256,6 @@ class $$MessageActionsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get contactId => $composableBuilder( column: $table.contactId, builder: (column) => ColumnFilters(column)); @@ -11309,9 +11297,6 @@ class $$MessageActionsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get contactId => $composableBuilder( column: $table.contactId, builder: (column) => ColumnOrderings(column)); @@ -11351,9 +11336,6 @@ class $$MessageActionsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get contactId => $composableBuilder(column: $table.contactId, builder: (column) => column); @@ -11407,32 +11389,32 @@ class $$MessageActionsTableTableManager extends RootTableManager< createComputedFieldComposer: () => $$MessageActionsTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ - Value id = const Value.absent(), Value messageId = const Value.absent(), Value contactId = const Value.absent(), Value type = const Value.absent(), Value actionAt = const Value.absent(), + Value rowid = const Value.absent(), }) => MessageActionsCompanion( - id: id, messageId: messageId, contactId: contactId, type: type, actionAt: actionAt, + rowid: rowid, ), createCompanionCallback: ({ - Value id = const Value.absent(), required String messageId, required int contactId, required MessageActionType type, Value actionAt = const Value.absent(), + Value rowid = const Value.absent(), }) => MessageActionsCompanion.insert( - id: id, messageId: messageId, contactId: contactId, type: type, actionAt: actionAt, + rowid: rowid, ), withReferenceMapper: (p0) => p0 .map((e) => ( diff --git a/lib/src/model/json/message_old.dart b/lib/src/model/json/message_old.dart deleted file mode 100644 index d783fc6..0000000 --- a/lib/src/model/json/message_old.dart +++ /dev/null @@ -1,331 +0,0 @@ -// ignore_for_file: strict_raw_type, prefer_constructors_over_static_methods - -import 'package:flutter/material.dart'; -import 'package:twonly/src/database/tables_old/messages_table.dart'; -import 'package:twonly/src/utils/misc.dart'; - -Color getMessageColorFromType(MessageContent content, BuildContext context) { - Color color; - - if (content is TextMessageContent) { - color = Colors.blueAccent; - } else { - if (content is MediaMessageContent) { - if (content.isRealTwonly) { - color = context.color.primary; - } else { - if (content.isVideo) { - color = const Color.fromARGB(255, 243, 33, 208); - } else { - color = Colors.redAccent; - } - } - } else { - return (isDarkMode(context)) ? Colors.white : Colors.black; - } - } - return color; -} - -extension MessageKindExtension on MessageKind { - String get name => toString().split('.').last; - - static MessageKind fromString(String name) { - return MessageKind.values.firstWhere((e) => e.name == name); - } -} - -class MessageJson { - MessageJson({ - required this.kind, - required this.content, - required this.timestamp, - this.messageReceiverId, - this.messageSenderId, - this.retransId, - }); - final MessageKind kind; - final MessageContent? content; - final int? messageReceiverId; - final int? messageSenderId; - int? retransId; - DateTime timestamp; - - @override - String toString() { - return 'Message(kind: $kind, content: $content, timestamp: $timestamp)'; - } - - static MessageJson fromJson(Map json) { - final kind = MessageKindExtension.fromString(json['kind'] as String); - - return MessageJson( - kind: kind, - messageReceiverId: (json['messageReceiverId'] as num?)?.toInt(), - messageSenderId: (json['messageSenderId'] as num?)?.toInt(), - retransId: (json['retransId'] as num?)?.toInt(), - content: MessageContent.fromJson( - kind, - json['content'] as Map, - ), - timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int), - ); - } - - Map toJson() => { - 'kind': kind.name, - 'content': content?.toJson(), - 'messageReceiverId': messageReceiverId, - 'messageSenderId': messageSenderId, - 'retransId': retransId, - 'timestamp': timestamp.toUtc().millisecondsSinceEpoch, - }; -} - -class MessageContent { - MessageContent(); - - static MessageContent? fromJson(MessageKind kind, Map json) { - switch (kind) { - case MessageKind.media: - return MediaMessageContent.fromJson(json); - case MessageKind.textMessage: - return TextMessageContent.fromJson(json); - case MessageKind.profileChange: - return ProfileContent.fromJson(json); - case MessageKind.pushKey: - return PushKeyContent.fromJson(json); - case MessageKind.reopenedMedia: - return ReopenedMediaFileContent.fromJson(json); - case MessageKind.flameSync: - return FlameSyncContent.fromJson(json); - case MessageKind.ack: - return AckContent.fromJson(json); - case MessageKind.signalDecryptError: - return SignalDecryptErrorContent.fromJson(json); - case MessageKind.storedMediaFile: - case MessageKind.contactRequest: - case MessageKind.rejectRequest: - case MessageKind.acceptRequest: - case MessageKind.opened: - case MessageKind.requestPushKey: - case MessageKind.receiveMediaError: - } - return null; - } - - Map toJson() { - return {}; - } -} - -class MediaMessageContent extends MessageContent { - MediaMessageContent({ - required this.maxShowTime, - required this.isRealTwonly, - required this.isVideo, - required this.mirrorVideo, - this.downloadToken, - this.encryptionKey, - this.encryptionMac, - this.encryptionNonce, - }); - final int maxShowTime; - final bool isRealTwonly; - final bool isVideo; - final bool mirrorVideo; - final List? downloadToken; - final List? encryptionKey; - final List? encryptionMac; - final List? encryptionNonce; - - static MediaMessageContent fromJson(Map json) { - return MediaMessageContent( - downloadToken: json['downloadToken'] == null - ? null - : List.from(json['downloadToken'] as List), - encryptionKey: json['encryptionKey'] == null - ? null - : List.from(json['encryptionKey'] as List), - encryptionMac: json['encryptionMac'] == null - ? null - : List.from(json['encryptionMac'] as List), - encryptionNonce: json['encryptionNonce'] == null - ? null - : List.from(json['encryptionNonce'] as List), - maxShowTime: json['maxShowTime'] as int, - isRealTwonly: json['isRealTwonly'] as bool, - isVideo: json['isVideo'] as bool? ?? false, - mirrorVideo: json['mirrorVideo'] as bool? ?? false, - ); - } - - @override - Map toJson() { - return { - 'downloadToken': downloadToken, - 'encryptionKey': encryptionKey, - 'encryptionMac': encryptionMac, - 'encryptionNonce': encryptionNonce, - 'isRealTwonly': isRealTwonly, - 'maxShowTime': maxShowTime, - 'isVideo': isVideo, - 'mirrorVideo': mirrorVideo, - }; - } -} - -class TextMessageContent extends MessageContent { - TextMessageContent({ - required this.text, - this.responseToMessageId, - this.responseToOtherMessageId, - }); - String text; - int? responseToMessageId; - int? responseToOtherMessageId; - - static TextMessageContent fromJson(Map json) { - return TextMessageContent( - text: json['text'] as String, - responseToOtherMessageId: json.containsKey('responseToOtherMessageId') - ? json['responseToOtherMessageId'] as int? - : null, - responseToMessageId: json.containsKey('responseToMessageId') - ? json['responseToMessageId'] as int? - : null, - ); - } - - @override - Map toJson() { - return { - 'text': text, - 'responseToMessageId': responseToMessageId, - 'responseToOtherMessageId': responseToOtherMessageId, - }; - } -} - -class ReopenedMediaFileContent extends MessageContent { - ReopenedMediaFileContent({required this.messageId}); - int messageId; - - static ReopenedMediaFileContent fromJson(Map json) { - return ReopenedMediaFileContent(messageId: json['messageId'] as int); - } - - @override - Map toJson() { - return {'messageId': messageId}; - } -} - -class SignalDecryptErrorContent extends MessageContent { - SignalDecryptErrorContent({required this.encryptedHash}); - List encryptedHash; - - static SignalDecryptErrorContent fromJson(Map json) { - return SignalDecryptErrorContent( - encryptedHash: List.from(json['encryptedHash'] as List), - ); - } - - @override - Map toJson() { - return { - 'encryptedHash': encryptedHash, - }; - } -} - -class AckContent extends MessageContent { - AckContent({required this.messageIdToAck, required this.retransIdToAck}); - int? messageIdToAck; - int retransIdToAck; - - static AckContent fromJson(Map json) { - return AckContent( - messageIdToAck: json['messageIdToAck'] as int?, - retransIdToAck: json['retransIdToAck'] as int, - ); - } - - @override - Map toJson() { - return { - 'messageIdToAck': messageIdToAck, - 'retransIdToAck': retransIdToAck, - }; - } -} - -class ProfileContent extends MessageContent { - ProfileContent({required this.avatarSvg, required this.displayName}); - String avatarSvg; - String displayName; - - static ProfileContent fromJson(Map json) { - return ProfileContent( - avatarSvg: json['avatarSvg'] as String, - displayName: json['displayName'] as String, - ); - } - - @override - Map toJson() { - return {'avatarSvg': avatarSvg, 'displayName': displayName}; - } -} - -class PushKeyContent extends MessageContent { - PushKeyContent({required this.keyId, required this.key}); - int keyId; - List key; - - static PushKeyContent fromJson(Map json) { - return PushKeyContent( - keyId: json['keyId'] as int, - key: List.from(json['key'] as List), - ); - } - - @override - Map toJson() { - return { - 'keyId': keyId, - 'key': key, - }; - } -} - -class FlameSyncContent extends MessageContent { - FlameSyncContent({ - required this.flameCounter, - required this.bestFriend, - required this.lastFlameCounterChange, - }); - int flameCounter; - DateTime lastFlameCounterChange; - bool bestFriend; - - static FlameSyncContent fromJson(Map json) { - return FlameSyncContent( - flameCounter: json['flameCounter'] as int, - bestFriend: json['bestFriend'] as bool, - lastFlameCounterChange: DateTime.fromMillisecondsSinceEpoch( - json['lastFlameCounterChange'] as int, - ), - ); - } - - @override - Map toJson() { - return { - 'flameCounter': flameCounter, - 'bestFriend': bestFriend, - 'lastFlameCounterChange': - lastFlameCounterChange.toUtc().millisecondsSinceEpoch, - }; - } -} diff --git a/lib/src/model/json/userdata.dart b/lib/src/model/json/userdata.dart index 987ff9f..00614b0 100644 --- a/lib/src/model/json/userdata.dart +++ b/lib/src/model/json/userdata.dart @@ -72,7 +72,7 @@ class UserData { List? tutorialDisplayed; - int? myBestFriendContactId; + String? myBestFriendGroupId; DateTime? signalLastSignedPreKeyUpdated; diff --git a/lib/src/model/json/userdata.g.dart b/lib/src/model/json/userdata.g.dart index a73b3ec..dd475e8 100644 --- a/lib/src/model/json/userdata.g.dart +++ b/lib/src/model/json/userdata.g.dart @@ -48,7 +48,7 @@ UserData _$UserDataFromJson(Map json) => UserData( ..tutorialDisplayed = (json['tutorialDisplayed'] as List?) ?.map((e) => e as String) .toList() - ..myBestFriendContactId = (json['myBestFriendContactId'] as num?)?.toInt() + ..myBestFriendGroupId = json['myBestFriendGroupId'] as String? ..signalLastSignedPreKeyUpdated = json['signalLastSignedPreKeyUpdated'] == null ? null @@ -97,7 +97,7 @@ Map _$UserDataToJson(UserData instance) => { 'lastPlanBallance': instance.lastPlanBallance, 'additionalUserInvites': instance.additionalUserInvites, 'tutorialDisplayed': instance.tutorialDisplayed, - 'myBestFriendContactId': instance.myBestFriendContactId, + 'myBestFriendGroupId': instance.myBestFriendGroupId, 'signalLastSignedPreKeyUpdated': instance.signalLastSignedPreKeyUpdated?.toIso8601String(), 'currentPreKeyIndexStart': instance.currentPreKeyIndexStart, diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart index 2facb77..a209170 100644 --- a/lib/src/model/protobuf/client/generated/messages.pb.dart +++ b/lib/src/model/protobuf/client/generated/messages.pb.dart @@ -663,14 +663,14 @@ class EncryptedContent_Media extends $pb.GeneratedMessage { class EncryptedContent_MediaUpdate extends $pb.GeneratedMessage { factory EncryptedContent_MediaUpdate({ EncryptedContent_MediaUpdate_Type? type, - $core.String? targetMessageId, + $core.String? targetMediaId, }) { final $result = create(); if (type != null) { $result.type = type; } - if (targetMessageId != null) { - $result.targetMessageId = targetMessageId; + if (targetMediaId != null) { + $result.targetMediaId = targetMediaId; } return $result; } @@ -680,7 +680,7 @@ class EncryptedContent_MediaUpdate extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.MediaUpdate', createEmptyInstance: create) ..e(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_MediaUpdate_Type.REOPENED, valueOf: EncryptedContent_MediaUpdate_Type.valueOf, enumValues: EncryptedContent_MediaUpdate_Type.values) - ..aOS(2, _omitFieldNames ? '' : 'targetMessageId', protoName: 'targetMessageId') + ..aOS(2, _omitFieldNames ? '' : 'targetMediaId', protoName: 'targetMediaId') ..hasRequiredFields = false ; @@ -715,13 +715,13 @@ class EncryptedContent_MediaUpdate extends $pb.GeneratedMessage { void clearType() => clearField(1); @$pb.TagNumber(2) - $core.String get targetMessageId => $_getSZ(1); + $core.String get targetMediaId => $_getSZ(1); @$pb.TagNumber(2) - set targetMessageId($core.String v) { $_setString(1, v); } + set targetMediaId($core.String v) { $_setString(1, v); } @$pb.TagNumber(2) - $core.bool hasTargetMessageId() => $_has(1); + $core.bool hasTargetMediaId() => $_has(1); @$pb.TagNumber(2) - void clearTargetMessageId() => clearField(2); + void clearTargetMediaId() => clearField(2); } class EncryptedContent_ContactRequest extends $pb.GeneratedMessage { @@ -1025,8 +1025,8 @@ class EncryptedContent_FlameSync extends $pb.GeneratedMessage { class EncryptedContent extends $pb.GeneratedMessage { factory EncryptedContent({ $core.String? groupId, + $core.bool? isDirectChat, $fixnum.Int64? senderProfileCounter, - EncryptedContent_TextMessage? textMessage, EncryptedContent_MessageUpdate? messageUpdate, EncryptedContent_Media? media, EncryptedContent_MediaUpdate? mediaUpdate, @@ -1035,17 +1035,18 @@ class EncryptedContent extends $pb.GeneratedMessage { EncryptedContent_FlameSync? flameSync, EncryptedContent_PushKeys? pushKeys, EncryptedContent_Reaction? reaction, + EncryptedContent_TextMessage? textMessage, }) { final $result = create(); if (groupId != null) { $result.groupId = groupId; } + if (isDirectChat != null) { + $result.isDirectChat = isDirectChat; + } if (senderProfileCounter != null) { $result.senderProfileCounter = senderProfileCounter; } - if (textMessage != null) { - $result.textMessage = textMessage; - } if (messageUpdate != null) { $result.messageUpdate = messageUpdate; } @@ -1070,6 +1071,9 @@ class EncryptedContent extends $pb.GeneratedMessage { if (reaction != null) { $result.reaction = reaction; } + if (textMessage != null) { + $result.textMessage = textMessage; + } return $result; } EncryptedContent._() : super(); @@ -1078,8 +1082,8 @@ class EncryptedContent extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent', createEmptyInstance: create) ..aOS(2, _omitFieldNames ? '' : 'groupId', protoName: 'groupId') - ..aInt64(3, _omitFieldNames ? '' : 'senderProfileCounter', protoName: 'senderProfileCounter') - ..aOM(4, _omitFieldNames ? '' : 'textMessage', protoName: 'textMessage', subBuilder: EncryptedContent_TextMessage.create) + ..aOB(3, _omitFieldNames ? '' : 'isDirectChat', protoName: 'isDirectChat') + ..aInt64(4, _omitFieldNames ? '' : 'senderProfileCounter', protoName: 'senderProfileCounter') ..aOM(5, _omitFieldNames ? '' : 'messageUpdate', protoName: 'messageUpdate', subBuilder: EncryptedContent_MessageUpdate.create) ..aOM(6, _omitFieldNames ? '' : 'media', subBuilder: EncryptedContent_Media.create) ..aOM(7, _omitFieldNames ? '' : 'mediaUpdate', protoName: 'mediaUpdate', subBuilder: EncryptedContent_MediaUpdate.create) @@ -1088,6 +1092,7 @@ class EncryptedContent extends $pb.GeneratedMessage { ..aOM(10, _omitFieldNames ? '' : 'flameSync', protoName: 'flameSync', subBuilder: EncryptedContent_FlameSync.create) ..aOM(11, _omitFieldNames ? '' : 'pushKeys', protoName: 'pushKeys', subBuilder: EncryptedContent_PushKeys.create) ..aOM(12, _omitFieldNames ? '' : 'reaction', subBuilder: EncryptedContent_Reaction.create) + ..aOM(13, _omitFieldNames ? '' : 'textMessage', protoName: 'textMessage', subBuilder: EncryptedContent_TextMessage.create) ..hasRequiredFields = false ; @@ -1121,26 +1126,24 @@ class EncryptedContent extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearGroupId() => clearField(2); - /// / This can be added, so the receiver can check weather he is up to date with the current profile @$pb.TagNumber(3) - $fixnum.Int64 get senderProfileCounter => $_getI64(1); + $core.bool get isDirectChat => $_getBF(1); @$pb.TagNumber(3) - set senderProfileCounter($fixnum.Int64 v) { $_setInt64(1, v); } + set isDirectChat($core.bool v) { $_setBool(1, v); } @$pb.TagNumber(3) - $core.bool hasSenderProfileCounter() => $_has(1); + $core.bool hasIsDirectChat() => $_has(1); @$pb.TagNumber(3) - void clearSenderProfileCounter() => clearField(3); + void clearIsDirectChat() => clearField(3); + /// / This can be added, so the receiver can check weather he is up to date with the current profile @$pb.TagNumber(4) - EncryptedContent_TextMessage get textMessage => $_getN(2); + $fixnum.Int64 get senderProfileCounter => $_getI64(2); @$pb.TagNumber(4) - set textMessage(EncryptedContent_TextMessage v) { setField(4, v); } + set senderProfileCounter($fixnum.Int64 v) { $_setInt64(2, v); } @$pb.TagNumber(4) - $core.bool hasTextMessage() => $_has(2); + $core.bool hasSenderProfileCounter() => $_has(2); @$pb.TagNumber(4) - void clearTextMessage() => clearField(4); - @$pb.TagNumber(4) - EncryptedContent_TextMessage ensureTextMessage() => $_ensure(2); + void clearSenderProfileCounter() => clearField(4); @$pb.TagNumber(5) EncryptedContent_MessageUpdate get messageUpdate => $_getN(3); @@ -1229,6 +1232,17 @@ class EncryptedContent extends $pb.GeneratedMessage { void clearReaction() => clearField(12); @$pb.TagNumber(12) EncryptedContent_Reaction ensureReaction() => $_ensure(10); + + @$pb.TagNumber(13) + EncryptedContent_TextMessage get textMessage => $_getN(11); + @$pb.TagNumber(13) + set textMessage(EncryptedContent_TextMessage v) { setField(13, v); } + @$pb.TagNumber(13) + $core.bool hasTextMessage() => $_has(11); + @$pb.TagNumber(13) + void clearTextMessage() => clearField(13); + @$pb.TagNumber(13) + EncryptedContent_TextMessage ensureTextMessage() => $_ensure(11); } diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index ef30eb5..f4c557c 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -95,8 +95,8 @@ const EncryptedContent$json = { '1': 'EncryptedContent', '2': [ {'1': 'groupId', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'groupId', '17': true}, - {'1': 'senderProfileCounter', '3': 3, '4': 1, '5': 3, '9': 1, '10': 'senderProfileCounter', '17': true}, - {'1': 'textMessage', '3': 4, '4': 1, '5': 11, '6': '.EncryptedContent.TextMessage', '9': 2, '10': 'textMessage', '17': true}, + {'1': 'isDirectChat', '3': 3, '4': 1, '5': 8, '9': 1, '10': 'isDirectChat', '17': true}, + {'1': 'senderProfileCounter', '3': 4, '4': 1, '5': 3, '9': 2, '10': 'senderProfileCounter', '17': true}, {'1': 'messageUpdate', '3': 5, '4': 1, '5': 11, '6': '.EncryptedContent.MessageUpdate', '9': 3, '10': 'messageUpdate', '17': true}, {'1': 'media', '3': 6, '4': 1, '5': 11, '6': '.EncryptedContent.Media', '9': 4, '10': 'media', '17': true}, {'1': 'mediaUpdate', '3': 7, '4': 1, '5': 11, '6': '.EncryptedContent.MediaUpdate', '9': 5, '10': 'mediaUpdate', '17': true}, @@ -105,12 +105,13 @@ const EncryptedContent$json = { {'1': 'flameSync', '3': 10, '4': 1, '5': 11, '6': '.EncryptedContent.FlameSync', '9': 8, '10': 'flameSync', '17': true}, {'1': 'pushKeys', '3': 11, '4': 1, '5': 11, '6': '.EncryptedContent.PushKeys', '9': 9, '10': 'pushKeys', '17': true}, {'1': 'reaction', '3': 12, '4': 1, '5': 11, '6': '.EncryptedContent.Reaction', '9': 10, '10': 'reaction', '17': true}, + {'1': 'textMessage', '3': 13, '4': 1, '5': 11, '6': '.EncryptedContent.TextMessage', '9': 11, '10': 'textMessage', '17': true}, ], '3': [EncryptedContent_TextMessage$json, EncryptedContent_Reaction$json, EncryptedContent_MessageUpdate$json, EncryptedContent_Media$json, EncryptedContent_MediaUpdate$json, EncryptedContent_ContactRequest$json, EncryptedContent_ContactUpdate$json, EncryptedContent_PushKeys$json, EncryptedContent_FlameSync$json], '8': [ {'1': '_groupId'}, + {'1': '_isDirectChat'}, {'1': '_senderProfileCounter'}, - {'1': '_textMessage'}, {'1': '_messageUpdate'}, {'1': '_media'}, {'1': '_mediaUpdate'}, @@ -119,6 +120,7 @@ const EncryptedContent$json = { {'1': '_flameSync'}, {'1': '_pushKeys'}, {'1': '_reaction'}, + {'1': '_textMessage'}, ], }; @@ -219,7 +221,7 @@ const EncryptedContent_MediaUpdate$json = { '1': 'MediaUpdate', '2': [ {'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.EncryptedContent.MediaUpdate.Type', '10': 'type'}, - {'1': 'targetMessageId', '3': 2, '4': 1, '5': 9, '10': 'targetMessageId'}, + {'1': 'targetMediaId', '3': 2, '4': 1, '5': 9, '10': 'targetMediaId'}, ], '4': [EncryptedContent_MediaUpdate_Type$json], }; @@ -315,59 +317,60 @@ const EncryptedContent_FlameSync$json = { /// Descriptor for `EncryptedContent`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( - 'ChBFbmNyeXB0ZWRDb250ZW50Eh0KB2dyb3VwSWQYAiABKAlIAFIHZ3JvdXBJZIgBARI3ChRzZW' - '5kZXJQcm9maWxlQ291bnRlchgDIAEoA0gBUhRzZW5kZXJQcm9maWxlQ291bnRlcogBARJECgt0' - 'ZXh0TWVzc2FnZRgEIAEoCzIdLkVuY3J5cHRlZENvbnRlbnQuVGV4dE1lc3NhZ2VIAlILdGV4dE' - '1lc3NhZ2WIAQESSgoNbWVzc2FnZVVwZGF0ZRgFIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuTWVz' - 'c2FnZVVwZGF0ZUgDUg1tZXNzYWdlVXBkYXRliAEBEjIKBW1lZGlhGAYgASgLMhcuRW5jcnlwdG' - 'VkQ29udGVudC5NZWRpYUgEUgVtZWRpYYgBARJECgttZWRpYVVwZGF0ZRgHIAEoCzIdLkVuY3J5' - 'cHRlZENvbnRlbnQuTWVkaWFVcGRhdGVIBVILbWVkaWFVcGRhdGWIAQESSgoNY29udGFjdFVwZG' - 'F0ZRgIIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZUgGUg1jb250YWN0VXBk' - 'YXRliAEBEk0KDmNvbnRhY3RSZXF1ZXN0GAkgASgLMiAuRW5jcnlwdGVkQ29udGVudC5Db250YW' - 'N0UmVxdWVzdEgHUg5jb250YWN0UmVxdWVzdIgBARI+CglmbGFtZVN5bmMYCiABKAsyGy5FbmNy' - 'eXB0ZWRDb250ZW50LkZsYW1lU3luY0gIUglmbGFtZVN5bmOIAQESOwoIcHVzaEtleXMYCyABKA' - 'syGi5FbmNyeXB0ZWRDb250ZW50LlB1c2hLZXlzSAlSCHB1c2hLZXlziAEBEjsKCHJlYWN0aW9u' - 'GAwgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgKUghyZWFjdGlvbogBARqpAQoLVG' - 'V4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoE' - 'dGV4dBgCIAEoCVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU' - '1lc3NhZ2VJZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQa' - 'gQEKCFJlYWN0aW9uEigKD3RhcmdldE1lc3NhZ2VJZBgBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEh' - 'kKBWVtb2ppGAIgASgJSABSBWVtb2ppiAEBEhsKBnJlbW92ZRgDIAEoCEgBUgZyZW1vdmWIAQFC' - 'CAoGX2Vtb2ppQgkKB19yZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLk' - 'VuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRlck1lc3Nh' - 'Z2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVTZW5kZXJNZXNzYW' - 'dlSWRzGAMgAygJUhhtdWx0aXBsZVNlbmRlck1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0' - 'ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEA' - 'ASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIHCgVfdGV4' - 'dBqMBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSMA' - 'oEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJDChpkaXNw' - 'bGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb2' - '5kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRp' - 'Y2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGA' - 'YgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxIAlINZG93' - 'bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQ' - 'ESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cHRp' - 'b25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQEiMwoEVHlwZRIMCghSRVVQTE9BRB' - 'AAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQA0IdChtfZGlzcGxheUxpbWl0SW5NaWxs' - 'aXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQhAKDl9lbmNyeX' - 'B0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2UapwEKC01lZGlh' - 'VXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cG' - 'VSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlw' - 'ZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db2' - '50YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVx' - 'dWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0' - 'VQVBACGtIBCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50' - 'LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRIhCglhdmF0YXJTdmcYAiABKAlIAFIJYXZhdGFyU3' - 'ZniAEBEiUKC2Rpc3BsYXlOYW1lGAMgASgJSAFSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoH' - 'UkVRVUVTVBAAEgoKBlVQREFURRABQgwKCl9hdmF0YXJTdmdCDgoMX2Rpc3BsYXlOYW1lGtUBCg' - 'hQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBl' - 'UgR0eXBlEhkKBWtleUlkGAIgASgDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQ' - 'ESIQoJY3JlYXRlZEF0GAQgASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQ' - 'ABIKCgZVUERBVEUQAUIICgZfa2V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GocBCglGbGFtZV' - 'N5bmMSIgoMZmxhbWVDb3VudGVyGAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291' - 'bnRlckNoYW5nZRgCIAEoA1IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGA' - 'MgASgIUgpiZXN0RnJpZW5kQgoKCF9ncm91cElkQhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIO' - 'CgxfdGV4dE1lc3NhZ2VCEAoOX21lc3NhZ2VVcGRhdGVCCAoGX21lZGlhQg4KDF9tZWRpYVVwZG' - 'F0ZUIQCg5fY29udGFjdFVwZGF0ZUIRCg9fY29udGFjdFJlcXVlc3RCDAoKX2ZsYW1lU3luY0IL' - 'CglfcHVzaEtleXNCCwoJX3JlYWN0aW9u'); + 'ChBFbmNyeXB0ZWRDb250ZW50Eh0KB2dyb3VwSWQYAiABKAlIAFIHZ3JvdXBJZIgBARInCgxpc0' + 'RpcmVjdENoYXQYAyABKAhIAVIMaXNEaXJlY3RDaGF0iAEBEjcKFHNlbmRlclByb2ZpbGVDb3Vu' + 'dGVyGAQgASgDSAJSFHNlbmRlclByb2ZpbGVDb3VudGVyiAEBEkoKDW1lc3NhZ2VVcGRhdGUYBS' + 'ABKAsyHy5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGVIA1INbWVzc2FnZVVwZGF0ZYgB' + 'ARIyCgVtZWRpYRgGIAEoCzIXLkVuY3J5cHRlZENvbnRlbnQuTWVkaWFIBFIFbWVkaWGIAQESRA' + 'oLbWVkaWFVcGRhdGUYByABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlSAVSC21l' + 'ZGlhVXBkYXRliAEBEkoKDWNvbnRhY3RVcGRhdGUYCCABKAsyHy5FbmNyeXB0ZWRDb250ZW50Lk' + 'NvbnRhY3RVcGRhdGVIBlINY29udGFjdFVwZGF0ZYgBARJNCg5jb250YWN0UmVxdWVzdBgJIAEo' + 'CzIgLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3RIB1IOY29udGFjdFJlcXVlc3SIAQ' + 'ESPgoJZmxhbWVTeW5jGAogASgLMhsuRW5jcnlwdGVkQ29udGVudC5GbGFtZVN5bmNICFIJZmxh' + 'bWVTeW5jiAEBEjsKCHB1c2hLZXlzGAsgASgLMhouRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5c0' + 'gJUghwdXNoS2V5c4gBARI7CghyZWFjdGlvbhgMIAEoCzIaLkVuY3J5cHRlZENvbnRlbnQuUmVh' + 'Y3Rpb25IClIIcmVhY3Rpb26IAQESRAoLdGV4dE1lc3NhZ2UYDSABKAsyHS5FbmNyeXB0ZWRDb2' + '50ZW50LlRleHRNZXNzYWdlSAtSC3RleHRNZXNzYWdliAEBGqkBCgtUZXh0TWVzc2FnZRIoCg9z' + 'ZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZX' + 'h0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGAQgASgJ' + 'SABSDnF1b3RlTWVzc2FnZUlkiAEBQhEKD19xdW90ZU1lc3NhZ2VJZBqBAQoIUmVhY3Rpb24SKA' + 'oPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQSGQoFZW1vamkYAiABKAlI' + 'AFIFZW1vammIAQESGwoGcmVtb3ZlGAMgASgISAFSBnJlbW92ZYgBAUIICgZfZW1vamlCCQoHX3' + 'JlbW92ZRq3AgoNTWVzc2FnZVVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVu' + 'dC5NZXNzYWdlVXBkYXRlLlR5cGVSBHR5cGUSLQoPc2VuZGVyTWVzc2FnZUlkGAIgASgJSABSD3' + 'NlbmRlck1lc3NhZ2VJZIgBARI6ChhtdWx0aXBsZVNlbmRlck1lc3NhZ2VJZHMYAyADKAlSGG11' + 'bHRpcGxlU2VuZGVyTWVzc2FnZUlkcxIXCgR0ZXh0GAQgASgJSAFSBHRleHSIAQESHAoJdGltZX' + 'N0YW1wGAUgASgDUgl0aW1lc3RhbXAiLQoEVHlwZRIKCgZERUxFVEUQABINCglFRElUX1RFWFQQ' + 'ARIKCgZPUEVORUQQAkISChBfc2VuZGVyTWVzc2FnZUlkQgcKBV90ZXh0GowFCgVNZWRpYRIoCg' + '9zZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGAIgASgOMhwu' + 'RW5jcnlwdGVkQ29udGVudC5NZWRpYS5UeXBlUgR0eXBlEkMKGmRpc3BsYXlMaW1pdEluTWlsbG' + 'lzZWNvbmRzGAMgASgDSABSGmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEjYKFnJlcXVp' + 'cmVzQXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZX' + 'N0YW1wGAUgASgDUgl0aW1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBiABKAlIAVIOcXVvdGVN' + 'ZXNzYWdlSWSIAQESKQoNZG93bmxvYWRUb2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEi' + 'kKDWVuY3J5cHRpb25LZXkYCCABKAxIA1INZW5jcnlwdGlvbktleYgBARIpCg1lbmNyeXB0aW9u' + 'TWFjGAkgASgMSARSDWVuY3J5cHRpb25NYWOIAQESLQoPZW5jcnlwdGlvbk5vbmNlGAogASgMSA' + 'VSD2VuY3J5cHRpb25Ob25jZYgBASIzCgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJ' + 'CgVWSURFTxACEgcKA0dJRhADQh0KG19kaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kc0IRCg9fcX' + 'VvdGVNZXNzYWdlSWRCEAoOX2Rvd25sb2FkVG9rZW5CEAoOX2VuY3J5cHRpb25LZXlCEAoOX2Vu' + 'Y3J5cHRpb25NYWNCEgoQX2VuY3J5cHRpb25Ob25jZRqjAQoLTWVkaWFVcGRhdGUSNgoEdHlwZR' + 'gBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVkaWFVcGRhdGUuVHlwZVIEdHlwZRIkCg10YXJn' + 'ZXRNZWRpYUlkGAIgASgJUg10YXJnZXRNZWRpYUlkIjYKBFR5cGUSDAoIUkVPUEVORUQQABIKCg' + 'ZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFjdFJlcXVlc3QSOQoEdHlw' + 'ZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3QuVHlwZVIEdHlwZSIrCg' + 'RUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQAhrSAQoNQ29udGFjdFVw' + 'ZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db250YWN0VXBkYXRlLlR5cG' + 'VSBHR5cGUSIQoJYXZhdGFyU3ZnGAIgASgJSABSCWF2YXRhclN2Z4gBARIlCgtkaXNwbGF5TmFt' + 'ZRgDIAEoCUgBUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVE' + 'UQAUIMCgpfYXZhdGFyU3ZnQg4KDF9kaXNwbGF5TmFtZRrVAQoIUHVzaEtleXMSMwoEdHlwZRgB' + 'IAEoDjIfLkVuY3J5cHRlZENvbnRlbnQuUHVzaEtleXMuVHlwZVIEdHlwZRIZCgVrZXlJZBgCIA' + 'EoA0gAUgVrZXlJZIgBARIVCgNrZXkYAyABKAxIAVIDa2V5iAEBEiEKCWNyZWF0ZWRBdBgEIAEo' + 'A0gCUgljcmVhdGVkQXSIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCCAoGX2' + 'tleUlkQgYKBF9rZXlCDAoKX2NyZWF0ZWRBdBqHAQoJRmxhbWVTeW5jEiIKDGZsYW1lQ291bnRl' + 'chgBIAEoA1IMZmxhbWVDb3VudGVyEjYKFmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2UYAiABKANSFm' + 'xhc3RGbGFtZUNvdW50ZXJDaGFuZ2USHgoKYmVzdEZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZEIK' + 'CghfZ3JvdXBJZEIPCg1faXNEaXJlY3RDaGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg' + '5fbWVzc2FnZVVwZGF0ZUIICgZfbWVkaWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBk' + 'YXRlQhEKD19jb250YWN0UmVxdWVzdEIMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcm' + 'VhY3Rpb25CDgoMX3RleHRNZXNzYWdl'); diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto index d8dc45d..cd37d58 100644 --- a/lib/src/model/protobuf/client/messages.proto +++ b/lib/src/model/protobuf/client/messages.proto @@ -30,11 +30,11 @@ message PlaintextContent { message EncryptedContent { optional string groupId = 2; + optional bool isDirectChat = 3; /// This can be added, so the receiver can check weather he is up to date with the current profile - optional int64 senderProfileCounter = 3; + optional int64 senderProfileCounter = 4; - optional TextMessage textMessage = 4; optional MessageUpdate messageUpdate = 5; optional Media media = 6; optional MediaUpdate mediaUpdate = 7; @@ -43,6 +43,7 @@ message EncryptedContent { optional FlameSync flameSync = 10; optional PushKeys pushKeys = 11; optional Reaction reaction = 12; + optional TextMessage textMessage = 13; message TextMessage { string senderMessageId = 1; @@ -98,7 +99,7 @@ message EncryptedContent { DECRYPTION_ERROR = 2; } Type type = 1; - string targetMessageId = 2; + string targetMediaId = 2; } message ContactRequest { diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.service.dart index 4b1b738..8da0d48 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.service.dart @@ -207,7 +207,8 @@ Future requestMediaReupload(String mediaId) async { final messages = await twonlyDB.messagesDao.getMessagesByMediaId(mediaId); if (messages.length != 1 || messages.first.senderId == null) { Log.error( - 'Media file has none or more than one sender. That is not possible'); + 'Media file has none or more than one sender. That is not possible', + ); return; } @@ -216,7 +217,7 @@ Future requestMediaReupload(String mediaId) async { EncryptedContent( mediaUpdate: EncryptedContent_MediaUpdate( type: EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR, - targetMessageId: mediaId, + targetMediaId: mediaId, ), ), ); diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index de6c9f3..6c8c783 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -119,6 +119,15 @@ Future _createUploadRequest(MediaFileService media) async { for (final message in messages) { final groupMembers = await twonlyDB.groupsDao.getGroupMembers(message.groupId); + + if (media.mediaFile.reuploadRequestedBy == null) { + await twonlyDB.groupsDao.incFlameCounter( + message.groupId, + false, + message.createdAt, + ); + } + for (final groupMember in groupMembers) { /// only send the upload to the users if (media.mediaFile.reuploadRequestedBy != null) { @@ -128,12 +137,6 @@ Future _createUploadRequest(MediaFileService media) async { } } - await twonlyDB.contactsDao.incFlameCounter( - groupMember.contactId, - false, - message.createdAt, - ); - final downloadToken = getRandomUint8List(32); var type = EncryptedContent_Media_Type.IMAGE; @@ -169,7 +172,8 @@ Future _createUploadRequest(MediaFileService media) async { if (cipherText == null) { Log.error( - 'Could not generate ciphertext message for ${groupMember.contactId}'); + 'Could not generate ciphertext message for ${groupMember.contactId}', + ); } final messageOnSuccess = TextMessage() diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index eaab030..1298115 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -161,11 +161,13 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ Future insertAndSendTextMessage( String groupId, String textMessage, + String? quotesMessageId, ) async { final message = await twonlyDB.messagesDao.insertMessage( MessagesCompanion( groupId: Value(groupId), content: Value(textMessage), + quotesMessageId: Value(quotesMessageId), ), ); if (message == null) { @@ -173,19 +175,40 @@ Future insertAndSendTextMessage( return; } + final encryptedContent = pb.EncryptedContent( + textMessage: pb.EncryptedContent_TextMessage( + senderMessageId: message.messageId, + text: textMessage, + timestamp: Int64(message.createdAt.millisecondsSinceEpoch), + ), + ); + + if (quotesMessageId != null) { + encryptedContent.textMessage.quoteMessageId = quotesMessageId; + } + + await sendCipherTextToGroup(groupId, encryptedContent); +} + +Future sendCipherTextToGroup( + String groupId, + pb.EncryptedContent encryptedContent, +) async { final groupMembers = await twonlyDB.groupsDao.getGroupMembers(groupId); + final group = await twonlyDB.groupsDao.getGroup(groupId); + if (group == null) return; + + encryptedContent + ..groupId = groupId + ..isDirectChat = group.isDirectChat; for (final groupMember in groupMembers) { - unawaited(sendCipherText( - groupMember.contactId, - pb.EncryptedContent( - textMessage: pb.EncryptedContent_TextMessage( - senderMessageId: message.messageId, - text: textMessage, - timestamp: Int64(message.createdAt.millisecondsSinceEpoch), - ), + unawaited( + sendCipherText( + groupMember.contactId, + encryptedContent, ), - )); + ); } } diff --git a/lib/src/services/api/server_messages/contact.server_messages.dart b/lib/src/services/api/server_messages/contact.server_messages.dart index d305800..2b3519e 100644 --- a/lib/src/services/api/server_messages/contact.server_messages.dart +++ b/lib/src/services/api/server_messages/contact.server_messages.dart @@ -82,24 +82,22 @@ Future handleFlameSync( EncryptedContent_FlameSync flameSync, ) async { Log.info('Got a flameSync from $contactId'); - final contact = await twonlyDB.contactsDao - .getContactByUserId(contactId) - .getSingleOrNull(); - if (contact == null || contact.lastFlameCounterChange != null) return; + final group = await twonlyDB.groupsDao.getDirectChat(contactId); + if (group == null || group.lastFlameCounterChange != null) return; - var updates = ContactsCompanion( + var updates = GroupsCompanion( alsoBestFriend: Value(flameSync.bestFriend), ); - if (isToday(contact.lastFlameCounterChange!) && + if (isToday(group.lastFlameCounterChange!) && isToday(fromTimestamp(flameSync.lastFlameCounterChange))) { - if (flameSync.flameCounter > contact.flameCounter) { - updates = ContactsCompanion( + if (flameSync.flameCounter > group.flameCounter) { + updates = GroupsCompanion( flameCounter: Value(flameSync.flameCounter.toInt()), ); } } - await twonlyDB.contactsDao.updateContact(contactId, updates); + await twonlyDB.groupsDao.updateGroup(group.groupId, updates); } Future checkForProfileUpdate( diff --git a/lib/src/services/api/server_messages/media.server_messages.dart b/lib/src/services/api/server_messages/media.server_messages.dart index 0b03728..b822fb4 100644 --- a/lib/src/services/api/server_messages/media.server_messages.dart +++ b/lib/src/services/api/server_messages/media.server_messages.dart @@ -100,8 +100,8 @@ Future handleMedia( ); if (message != null) { Log.info('Inserted a new media message with ID: ${message.messageId}'); - await twonlyDB.contactsDao.incFlameCounter( - fromUserId, + await twonlyDB.groupsDao.incFlameCounter( + message.groupId, true, fromTimestamp(media.timestamp), ); @@ -115,15 +115,17 @@ Future handleMediaUpdate( String groupId, EncryptedContent_MediaUpdate mediaUpdate, ) async { - final message = await twonlyDB.messagesDao - .getMessageById(mediaUpdate.targetMessageId) - .getSingleOrNull(); - if (message == null || message.mediaId == null) return; + final messages = await twonlyDB.messagesDao + .getMessagesByMediaId(mediaUpdate.targetMediaId); + if (messages.length != 1) return; + final message = messages.first; + if (message.senderId != fromUserId) return; final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(message.mediaId!); if (mediaFile == null) { Log.info( - 'Got media file update, but media file was not found ${message.mediaId}'); + 'Got media file update, but media file was not found ${message.mediaId}', + ); return; } diff --git a/lib/src/services/api/server_messages/messages.server_messages.dart b/lib/src/services/api/server_messages/messages.server_messages.dart index 6c7347a..b0b2457 100644 --- a/lib/src/services/api/server_messages/messages.server_messages.dart +++ b/lib/src/services/api/server_messages/messages.server_messages.dart @@ -10,7 +10,8 @@ Future handleMessageUpdate( switch (messageUpdate.type) { case EncryptedContent_MessageUpdate_Type.OPENED: Log.info( - 'Opened message ${messageUpdate.multipleSenderMessageIds.length}'); + 'Opened message ${messageUpdate.multipleSenderMessageIds.length}', + ); for (final senderMessageId in messageUpdate.multipleSenderMessageIds) { await twonlyDB.messagesDao.handleMessageOpened( contactId, diff --git a/lib/src/services/api/utils.dart b/lib/src/services/api/utils.dart index 1bc0674..e0a101e 100644 --- a/lib/src/services/api/utils.dart +++ b/lib/src/services/api/utils.dart @@ -88,7 +88,7 @@ Future handleMediaError(MediaFile media) async { EncryptedContent( mediaUpdate: EncryptedContent_MediaUpdate( type: EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR, - targetMessageId: message.messageId, + targetMediaId: message.mediaId, ), ), ); diff --git a/lib/src/services/flame.service.dart b/lib/src/services/flame.service.dart index 1f98386..c28d59e 100644 --- a/lib/src/services/flame.service.dart +++ b/lib/src/services/flame.service.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:fixnum/fixnum.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/database/daos/groups.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; @@ -10,49 +10,52 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; Future syncFlameCounters() async { - final user = await getUser(); - if (user == null) return; - - final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts(); - if (contacts.isEmpty) return; - final maxMessageCounter = contacts.map((x) => x.totalMediaCounter).max; + final groups = await twonlyDB.groupsDao.getAllDirectChats(); + if (groups.isEmpty) return; + final maxMessageCounter = groups.map((x) => x.totalMediaCounter).max; final bestFriend = - contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter); + groups.firstWhere((x) => x.totalMediaCounter == maxMessageCounter); - if (user.myBestFriendContactId != bestFriend.userId) { + if (gUser.myBestFriendGroupId != bestFriend.groupId) { await updateUserdata((user) { - user.myBestFriendContactId = bestFriend.userId; + user.myBestFriendGroupId = bestFriend.groupId; return user; }); } - for (final contact in contacts) { - if (contact.lastFlameCounterChange == null || contact.deleted) continue; - if (!isToday(contact.lastFlameCounterChange!)) continue; - if (contact.lastFlameSync != null) { - if (isToday(contact.lastFlameSync!)) continue; + for (final group in groups) { + if (group.lastFlameCounterChange == null) continue; + if (!isToday(group.lastFlameCounterChange!)) continue; + if (group.lastFlameSync != null) { + if (isToday(group.lastFlameSync!)) continue; } - final flameCounter = getFlameCounterFromContact(contact) - 1; + final flameCounter = getFlameCounterFromGroup(group) - 1; - // only sync when flame counter is higher than three days - if (flameCounter < 1 && bestFriend.userId != contact.userId) continue; + // only sync when flame counter is higher than three days or when they are bestFriends + if (flameCounter < 1 && bestFriend.groupId != group.groupId) continue; + + final groupMembers = + await twonlyDB.groupsDao.getGroupMembers(group.groupId); + if (groupMembers.length != 1) { + continue; // flame sync is only done for groups of two + } await sendCipherText( - contact.userId, + groupMembers.first.contactId, EncryptedContent( flameSync: EncryptedContent_FlameSync( flameCounter: Int64(flameCounter), lastFlameCounterChange: - Int64(contact.lastFlameCounterChange!.millisecondsSinceEpoch), - bestFriend: contact.userId == bestFriend.userId, + Int64(group.lastFlameCounterChange!.millisecondsSinceEpoch), + bestFriend: group.groupId == bestFriend.groupId, ), ), ); - await twonlyDB.contactsDao.updateContact( - contact.userId, - ContactsCompanion( + await twonlyDB.groupsDao.updateGroup( + group.groupId, + GroupsCompanion( lastFlameSync: Value(DateTime.now()), ), ); diff --git a/lib/src/services/mediafiles/mediafile.service.dart b/lib/src/services/mediafiles/mediafile.service.dart index 2a062d2..73d5a78 100644 --- a/lib/src/services/mediafiles/mediafile.service.dart +++ b/lib/src/services/mediafiles/mediafile.service.dart @@ -119,7 +119,7 @@ class MediaFileService { originalPath, storedPath, thumbnailPath, - uploadRequestPath + uploadRequestPath, ]; for (final path in pathsToRemove) { @@ -146,11 +146,13 @@ class MediaFileService { String namePrefix = '', String extensionParam = '', }) { - final mediaBaseDir = Directory(join( - applicationSupportDirectory.path, - 'mediafiles', - directory, - )); + final mediaBaseDir = Directory( + join( + applicationSupportDirectory.path, + 'mediafiles', + directory, + ), + ); if (!mediaBaseDir.existsSync()) { mediaBaseDir.createSync(recursive: true); } diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index e217869..a1fa18a 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -1,5 +1,4 @@ import 'dart:math'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; @@ -8,11 +7,14 @@ import 'package:intl/intl.dart'; import 'package:local_auth/local_auth.dart'; import 'package:pie_menu/pie_menu.dart'; import 'package:provider/provider.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; +import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/providers/settings.provider.dart'; import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/misc.dart'; extension ShortCutsExtension on BuildContext { AppLocalizations get lang => AppLocalizations.of(this)!; @@ -284,3 +286,28 @@ PieTheme getPieCanvasTheme(BuildContext context) { ), ); } + +Color getMessageColorFromType( + Message message, + MediaFile? mediaFile, + BuildContext context, +) { + Color color; + + if (message.type == MessageType.text) { + color = Colors.blueAccent; + } else if (mediaFile != null) { + if (mediaFile.requiresAuthentication) { + color = context.color.primary; + } else { + if (mediaFile.type == MediaType.video) { + color = const Color.fromARGB(255, 243, 33, 208); + } else { + color = Colors.redAccent; + } + } + } else { + return (isDarkMode(context)) ? Colors.white : Colors.black; + } + return color; +} diff --git a/lib/src/views/camera/share_image_components/best_friends_selector.dart b/lib/src/views/camera/share_image_components/best_friends_selector.dart index f63cba5..491abf9 100644 --- a/lib/src/views/camera/share_image_components/best_friends_selector.dart +++ b/lib/src/views/camera/share_image_components/best_friends_selector.dart @@ -1,37 +1,31 @@ -// ignore_for_file: strict_raw_type - import 'dart:collection'; - import 'package:flutter/material.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/headline.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; class BestFriendsSelector extends StatelessWidget { const BestFriendsSelector({ - required this.users, - required this.isRealTwonly, - required this.updateStatus, - required this.selectedUserIds, + required this.groups, + required this.selectedGroupIds, + required this.updateSelectedGroupIds, required this.title, + required this.showSelectAll, super.key, }); - final List users; - final void Function(int, bool) updateStatus; - final HashSet selectedUserIds; - final bool isRealTwonly; + final List groups; + final HashSet selectedGroupIds; + final void Function(String, bool) updateSelectedGroupIds; final String title; + final bool showSelectAll; @override Widget build(BuildContext context) { - if (users.isEmpty) { + if (groups.isEmpty) { return Container(); } - return Column( children: [ Row( @@ -39,11 +33,11 @@ class BestFriendsSelector extends StatelessWidget { Expanded( child: HeadLineComponent(title), ), - if (!isRealTwonly) + if (showSelectAll) GestureDetector( onTap: () { - for (final user in users) { - updateStatus(user.userId, true); + for (final group in groups) { + updateSelectedGroupIds(group.groupId, true); } }, child: Container( @@ -70,7 +64,7 @@ class BestFriendsSelector extends StatelessWidget { Column( spacing: 8, children: List.generate( - (users.length + 1) ~/ 2, + (groups.length + 1) ~/ 2, (rowIndex) { final firstUserIndex = rowIndex * 2; final secondUserIndex = firstUserIndex + 1; @@ -79,21 +73,19 @@ class BestFriendsSelector extends StatelessWidget { children: [ Expanded( child: UserCheckbox( - isChecked: selectedUserIds - .contains(users[firstUserIndex].userId), - user: users[firstUserIndex], - onChanged: updateStatus, - isRealTwonly: isRealTwonly, + isChecked: selectedGroupIds + .contains(groups[firstUserIndex].groupId), + group: groups[firstUserIndex], + onChanged: updateSelectedGroupIds, ), ), - if (secondUserIndex < users.length) + if (secondUserIndex < groups.length) Expanded( child: UserCheckbox( - isChecked: selectedUserIds - .contains(users[secondUserIndex].userId), - user: users[secondUserIndex], - onChanged: updateStatus, - isRealTwonly: isRealTwonly, + isChecked: selectedGroupIds + .contains(groups[secondUserIndex].groupId), + group: groups[secondUserIndex], + onChanged: updateSelectedGroupIds, ), ) else @@ -112,28 +104,24 @@ class BestFriendsSelector extends StatelessWidget { class UserCheckbox extends StatelessWidget { const UserCheckbox({ - required this.user, + required this.group, required this.onChanged, - required this.isRealTwonly, required this.isChecked, super.key, }); - final Contact user; - final void Function(int, bool) onChanged; + final Group group; + final void Function(String, bool) onChanged; final bool isChecked; - final bool isRealTwonly; @override Widget build(BuildContext context) { - final displayName = getContactDisplayName(user); - return Container( padding: const EdgeInsets.symmetric( horizontal: 3, ), // Padding inside the container child: GestureDetector( onTap: () { - onChanged(user.userId, !isChecked); + onChanged(group.groupId, !isChecked); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10), @@ -149,8 +137,8 @@ class UserCheckbox extends StatelessWidget { ), child: Row( children: [ - ContactAvatar( - contact: user, + AvatarIcon( + group: group, fontSize: 12, ), const SizedBox(width: 8), @@ -160,28 +148,21 @@ class UserCheckbox extends StatelessWidget { Row( children: [ Text( - displayName.length > 8 - ? '${displayName.substring(0, 8)}...' - : displayName, + group.groupName.length > 12 + ? '${group.groupName.substring(0, 9)}...' + : group.groupName, overflow: TextOverflow.ellipsis, ), ], ), - StreamBuilder( - stream: twonlyDB.contactsDao.watchFlameCounter(user.userId), - builder: (context, snapshot) { - if (!snapshot.hasData || snapshot.data! == 0) { - return Container(); - } - return FlameCounterWidget(user, snapshot.data!); - }, - ), + FlameCounterWidget(groupId: group.groupId), ], ), Expanded(child: Container()), Checkbox( value: isChecked, side: WidgetStateBorderSide.resolveWith( + // ignore: strict_raw_type (Set states) { if (states.contains(WidgetState.selected)) { return const BorderSide(width: 0); @@ -192,7 +173,7 @@ class UserCheckbox extends StatelessWidget { }, ), onChanged: (bool? value) { - onChanged(user.userId, value ?? false); + onChanged(group.groupId, value ?? false); }, ), ], diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart index ba13f90..e5d787a 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor_view.dart @@ -186,7 +186,8 @@ class _ShareImageEditorView extends State { onPressed: () async { if (media.type != MediaType.video) { await mediaService.setDisplayLimit( - (media.displayLimitInMilliseconds == null) ? 0 : null); + (media.displayLimitInMilliseconds == null) ? 0 : null, + ); if (!mounted) return; setState(() {}); return; @@ -465,8 +466,9 @@ class _ShareImageEditorView extends State { : const FaIcon(FontAwesomeIcons.solidPaperPlane), onPressed: () async { if (sendingOrLoadingImage) return; - if (widget.sendToGroup == null) + if (widget.sendToGroup == null) { return pushShareImageView(); + } await sendImageToSinglePerson(); }, style: ButtonStyle( diff --git a/lib/src/views/camera/share_image_view.dart b/lib/src/views/camera/share_image_view.dart index 13629ef..92cb735 100644 --- a/lib/src/views/camera/share_image_view.dart +++ b/lib/src/views/camera/share_image_view.dart @@ -2,23 +2,19 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/database/daos/groups.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/headline.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; -import 'package:twonly/src/views/components/verified_shield.dart'; -import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; class ShareImageView extends StatefulWidget { const ShareImageView({ @@ -38,28 +34,27 @@ class ShareImageView extends StatefulWidget { } class _ShareImageView extends State { - List contacts = []; - List _otherUsers = []; - List _bestFriends = []; - List _pinnedContacts = []; - Uint8List? imageBytes; + List contacts = []; + List _otherUsers = []; + List _bestFriends = []; + List _pinnedContacts = []; + bool sendingImage = false; + bool mediaStoreFutureReady = false; bool hideArchivedUsers = true; final TextEditingController searchUserName = TextEditingController(); - late StreamSubscription> contactSub; + late StreamSubscription> allGroupSub; String lastQuery = ''; @override void initState() { super.initState(); - final allContacts = twonlyDB.contactsDao.watchContactsForShareView(); - - contactSub = allContacts.listen((allContacts) async { + allGroupSub = twonlyDB.groupsDao.watchGroups().listen((allGroups) async { setState(() { - contacts = allContacts; + contacts = allGroups; }); - await updateUsers(allContacts.where((x) => !x.archived).toList()); + await updateGroups(allGroups.where((x) => !x.archived).toList()); }); unawaited(initAsync()); @@ -69,6 +64,7 @@ class _ShareImageView extends State { if (widget.mediaStoreFuture != null) { await widget.mediaStoreFuture; } + mediaStoreFutureReady = true; await widget.mediaFileService.setUploadState(UploadState.preprocessing); unawaited(startBackgroundMediaUpload(widget.mediaFileService)); if (!mounted) return; @@ -77,16 +73,17 @@ class _ShareImageView extends State { @override void dispose() { - unawaited(contactSub.cancel()); + unawaited(allGroupSub.cancel()); super.dispose(); } - Future updateUsers(List users) async { + Future updateGroups(List groups) async { // Sort contacts by flameCounter and then by totalMediaCounter - users.sort((a, b) { + groups.sort((a, b) { // First, compare by flameCounter - final flameComparison = getFlameCounterFromContact(b) - .compareTo(getFlameCounterFromContact(a)); + + final flameComparison = + getFlameCounterFromGroup(b).compareTo(getFlameCounterFromGroup(a)); if (flameComparison != 0) { return flameComparison; // Sort by flameCounter in descending order } @@ -97,18 +94,18 @@ class _ShareImageView extends State { }); // Separate best friends and other users - final bestFriends = []; - final otherUsers = []; - final pinnedContacts = users.where((c) => c.pinned).toList(); + final bestFriends = []; + final otherUsers = []; + final pinnedContacts = groups.where((c) => c.pinned).toList(); - for (final contact in users) { - if (contact.pinned) continue; - if (!contact.archived && - (getFlameCounterFromContact(contact)) > 0 && + for (final group in groups) { + if (group.pinned) continue; + if (!group.archived && + (getFlameCounterFromGroup(group)) > 0 && bestFriends.length < 6) { - bestFriends.add(contact); + bestFriends.add(group); } else { - otherUsers.add(contact); + otherUsers.add(group); } } @@ -122,13 +119,13 @@ class _ShareImageView extends State { Future _filterUsers(String query) async { lastQuery = query; if (query.isEmpty) { - await updateUsers( + await updateGroups( contacts .where( (x) => !x.archived || !hideArchivedUsers || - widget.selectedUserIds.contains(x.userId), + widget.selectedGroupIds.contains(x.groupId), ) .toList(), ); @@ -136,16 +133,14 @@ class _ShareImageView extends State { } final usersFiltered = contacts .where( - (user) => getContactDisplayName(user) - .toLowerCase() - .contains(query.toLowerCase()), + (user) => user.groupName.toLowerCase().contains(query.toLowerCase()), ) .toList(); - await updateUsers(usersFiltered); + await updateGroups(usersFiltered); } - void updateStatus(int userId, bool checked) { - widget.updateStatus(userId, checked); + void updateSelectedGroupIds(String groupId, bool checked) { + widget.updateSelectedGroupIds(groupId, checked); setState(() {}); } @@ -173,19 +168,21 @@ class _ShareImageView extends State { ), if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10), BestFriendsSelector( - users: _pinnedContacts, - selectedUserIds: widget.selectedUserIds, - isRealTwonly: widget.isRealTwonly, - updateStatus: updateStatus, + groups: _pinnedContacts, + selectedGroupIds: widget.selectedGroupIds, + updateSelectedGroupIds: updateSelectedGroupIds, title: context.lang.shareImagePinnedContacts, + showSelectAll: + !widget.mediaFileService.mediaFile.requiresAuthentication, ), const SizedBox(height: 10), BestFriendsSelector( - users: _bestFriends, - selectedUserIds: widget.selectedUserIds, - isRealTwonly: widget.isRealTwonly, - updateStatus: updateStatus, + groups: _bestFriends, + selectedGroupIds: widget.selectedGroupIds, + updateSelectedGroupIds: updateSelectedGroupIds, title: context.lang.shareImageBestFriends, + showSelectAll: + !widget.mediaFileService.mediaFile.requiresAuthentication, ), const SizedBox(height: 10), if (_otherUsers.isNotEmpty) @@ -229,9 +226,8 @@ class _ShareImageView extends State { Expanded( child: UserList( List.from(_otherUsers), - selectedUserIds: widget.selectedUserIds, - isRealTwonly: widget.isRealTwonly, - updateStatus: updateStatus, + selectedGroupIds: widget.selectedGroupIds, + updateSelectedGroupIds: updateSelectedGroupIds, ), ), ], @@ -246,7 +242,7 @@ class _ShareImageView extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ FilledButton.icon( - icon: imageBytes == null || sendingImage + icon: !mediaStoreFutureReady || sendingImage ? SizedBox( height: 12, width: 12, @@ -257,50 +253,28 @@ class _ShareImageView extends State { ) : const FaIcon(FontAwesomeIcons.solidPaperPlane), onPressed: () async { - if (imageBytes == null || widget.selectedUserIds.isEmpty) { + if (!mediaStoreFutureReady || + widget.selectedGroupIds.isEmpty) { return; } - final err = await isAllowedToSend(); - if (!context.mounted) return; + setState(() { + sendingImage = true; + }); - if (err != null) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return SubscriptionView( - redirectError: err, - ); - }, - ), - ); - } else { - setState(() { - sendingImage = true; - }); + await insertMediaFileInMessagesTable( + widget.mediaFileService, + widget.selectedGroupIds.toList(), + ); - await finalizeUpload( - widget.mediaUploadId, - widget.selectedUserIds.toList(), - widget.isRealTwonly, - widget.videoUploadHandler != null, - widget.mirrorVideo, - widget.maxShowTime, - ); - - /// trigger the upload of the media file. - unawaited(handleNextMediaUploadSteps(widget.mediaUploadId)); - - if (context.mounted) { - Navigator.pop(context, true); - // if (widget.preselectedUser != null) { - // Navigator.pop(context, true); - // } else { - // Navigator.popUntil(context, (route) => route.isFirst, true); - // globalUpdateOfHomeViewPageIndex(1); - // } - } + if (context.mounted) { + Navigator.pop(context, true); + // if (widget.preselectedUser != null) { + // Navigator.pop(context, true); + // } else { + // Navigator.popUntil(context, (route) => route.isFirst, true); + // globalUpdateOfHomeViewPageIndex(1); + // } } }, style: ButtonStyle( @@ -308,7 +282,7 @@ class _ShareImageView extends State { const EdgeInsets.symmetric(vertical: 10, horizontal: 30), ), backgroundColor: WidgetStateProperty.all( - imageBytes == null || widget.selectedUserIds.isEmpty + mediaStoreFutureReady || widget.selectedGroupIds.isEmpty ? Theme.of(context).colorScheme.secondary : Theme.of(context).colorScheme.primary, ), @@ -328,52 +302,42 @@ class _ShareImageView extends State { class UserList extends StatelessWidget { const UserList( - this.users, { - required this.selectedUserIds, - required this.updateStatus, - required this.isRealTwonly, + this.groups, { + required this.selectedGroupIds, + required this.updateSelectedGroupIds, super.key, }); - final void Function(int, bool) updateStatus; - final List users; - final bool isRealTwonly; - final HashSet selectedUserIds; + final void Function(String, bool) updateSelectedGroupIds; + final List groups; + final HashSet selectedGroupIds; @override Widget build(BuildContext context) { // Step 1: Sort the users alphabetically - users + groups .sort((a, b) => b.lastMessageExchange.compareTo(a.lastMessageExchange)); return ListView.builder( restorationId: 'new_message_users_list', - itemCount: users.length, + itemCount: groups.length, itemBuilder: (BuildContext context, int i) { - final user = users[i]; - final flameCounter = getFlameCounterFromContact(user); + final group = groups[i]; return ListTile( title: Row( children: [ - if (isRealTwonly) - Padding( - padding: const EdgeInsets.only(right: 1), - child: VerifiedShield(user), - ), - Text(getContactDisplayName(user)), - if (flameCounter >= 1) - FlameCounterWidget( - user, - flameCounter, - prefix: true, - ), + Text(group.groupName), + FlameCounterWidget( + groupId: group.groupId, + prefix: true, + ), ], ), - leading: ContactAvatar( - contact: user, + leading: AvatarIcon( + group: group, fontSize: 15, ), trailing: Checkbox( - value: selectedUserIds.contains(user.userId), + value: selectedGroupIds.contains(group.groupId), side: WidgetStateBorderSide.resolveWith( (Set states) { if (states.contains(WidgetState.selected)) { @@ -384,11 +348,14 @@ class UserList extends StatelessWidget { ), onChanged: (bool? value) { if (value == null) return; - updateStatus(user.userId, value); + updateSelectedGroupIds(group.groupId, value); }, ), onTap: () { - updateStatus(user.userId, !selectedUserIds.contains(user.userId)); + updateSelectedGroupIds( + group.groupId, + !selectedGroupIds.contains(group.groupId), + ); }, ); }, diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index 64e5089..dedf439 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -1,15 +1,12 @@ import 'dart:async'; - import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.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.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; -import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; @@ -17,8 +14,8 @@ import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/headline.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; class AddNewUserView extends StatefulWidget { const AddNewUserView({super.key}); @@ -97,19 +94,18 @@ class _SearchUsernameView extends State { if (added > 0) { if (await createNewSignalSession(userdata)) { - // before notifying the other party, add + // 1. Setup notifications keys with the other user await setupNotificationWithUsers( forceContact: userdata.userId.toInt(), ); - await encryptAndSendMessageAsync( - null, + // 2. Then send user request + await sendCipherText( userdata.userId.toInt(), - MessageJson( - kind: MessageKind.contactRequest, - timestamp: DateTime.now(), - content: MessageContent(), + EncryptedContent( + contactRequest: EncryptedContent_ContactRequest( + type: EncryptedContent_ContactRequest_Type.REQUEST, + ), ), - pushNotification: PushNotification(kind: PushKind.contactRequest), ); } } @@ -198,7 +194,7 @@ class ContactsListView extends StatelessWidget { child: IconButton( icon: const FaIcon(FontAwesomeIcons.boxArchive, size: 15), onPressed: () async { - const update = ContactsCompanion(archived: Value(true)); + const update = ContactsCompanion(requested: Value(false)); await twonlyDB.contactsDao.updateContact(contact.userId, update); }, ), @@ -234,17 +230,18 @@ class ContactsListView extends StatelessWidget { IconButton( icon: const Icon(Icons.check, color: Colors.green), onPressed: () async { - const update = ContactsCompanion(accepted: Value(true)); + const update = ContactsCompanion( + accepted: Value(true), + requested: Value(false), + ); await twonlyDB.contactsDao.updateContact(contact.userId, update); - await encryptAndSendMessageAsync( - null, + await sendCipherText( contact.userId, - MessageJson( - kind: MessageKind.acceptRequest, - timestamp: DateTime.now(), - content: MessageContent(), + EncryptedContent( + contactRequest: EncryptedContent_ContactRequest( + type: EncryptedContent_ContactRequest_Type.ACCEPT, + ), ), - pushNotification: PushNotification(kind: PushKind.acceptRequest), ); await notifyContactsAboutProfileChange(); }, @@ -261,7 +258,7 @@ class ContactsListView extends StatelessWidget { final displayName = getContactDisplayName(contact); return ListTile( title: Text(displayName), - leading: ContactAvatar(contact: contact), + leading: AvatarIcon(contact: contact), trailing: Row( mainAxisSize: MainAxisSize.min, children: contact.requested diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index 302ad66..adde0ea 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -7,27 +6,18 @@ import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/providers/connection.provider.dart'; -import 'package:twonly/src/services/api/mediafiles/download.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/chats/chat_list_components/backup_notice.card.dart'; import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp.dart'; import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart'; -import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart'; -import 'package:twonly/src/views/chats/chat_messages.view.dart'; -import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart'; -import 'package:twonly/src/views/chats/media_viewer.view.dart'; +import 'package:twonly/src/views/chats/chat_list_components/group_list_item.dart'; import 'package:twonly/src/views/chats/start_new_chat.view.dart'; -import 'package:twonly/src/views/components/flame.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/notification_badge.dart'; -import 'package:twonly/src/views/components/user_context_menu.component.dart'; import 'package:twonly/src/views/settings/help/changelog.view.dart'; import 'package:twonly/src/views/settings/profile/profile.view.dart'; import 'package:twonly/src/views/settings/settings_main.view.dart'; @@ -41,10 +31,9 @@ class ChatListView extends StatefulWidget { } class _ChatListViewState extends State { - late StreamSubscription> _contactsSub; - List _contacts = []; - List _pinnedContacts = []; - UserData? _user; + late StreamSubscription> _contactsSub; + List _groupsNotPinned = []; + List _groupsPinned = []; GlobalKey firstUserListItemKey = GlobalKey(); GlobalKey searchForOtherUsers = GlobalKey(); @@ -58,11 +47,11 @@ class _ChatListViewState extends State { } Future initAsync() async { - final stream = twonlyDB.contactsDao.watchContactsForChatList(); - _contactsSub = stream.listen((contacts) { + final stream = twonlyDB.groupsDao.watchGroups(); + _contactsSub = stream.listen((groups) { setState(() { - _contacts = contacts.where((x) => !x.pinned).toList(); - _pinnedContacts = contacts.where((x) => x.pinned).toList(); + _groupsNotPinned = groups.where((x) => !x.pinned).toList(); + _groupsPinned = groups.where((x) => x.pinned).toList(); }); }); @@ -71,21 +60,16 @@ class _ChatListViewState extends State { if (!mounted) return; await showChatListTutorialSearchOtherUsers(context, searchForOtherUsers); if (!mounted) return; - if (_contacts.isNotEmpty) { + if (_groupsNotPinned.isNotEmpty) { await showChatListTutorialContextMenu(context, firstUserListItemKey); } }); - final user = await getUser(); - if (user == null) return; - setState(() { - _user = user; - }); final changeLog = await rootBundle.loadString('CHANGELOG.md'); final changeLogHash = (await compute(Sha256().hash, changeLog.codeUnits)).bytes; - if (!user.hideChangeLog && - user.lastChangeLogHash.toString() != changeLogHash.toString()) { + if (!gUser.hideChangeLog && + gUser.lastChangeLogHash.toString() != changeLogHash.toString()) { await updateUserdata((u) { u.lastChangeLogHash = changeLogHash; return u; @@ -93,7 +77,7 @@ class _ChatListViewState extends State { if (!mounted) return; // only show changelog to people who already have contacts // this prevents that this is shown directly after the user registered - if (_contacts.isNotEmpty) { + if (_groupsNotPinned.isNotEmpty) { await Navigator.push( context, MaterialPageRoute( @@ -133,12 +117,11 @@ class _ChatListViewState extends State { }, ), ); - _user = await getUser(); if (!mounted) return; - setState(() {}); + setState(() {}); // gUser has updated }, - child: ContactAvatar( - userData: _user, + child: AvatarIcon( + userData: gUser, fontSize: 14, color: context.color.onSurface.withAlpha(20), ), @@ -210,9 +193,8 @@ class _ChatListViewState extends State { builder: (context) => const SettingsMainView(), ), ); - _user = await getUser(); if (!mounted) return; - setState(() {}); + setState(() {}); // gUser may has changed... }, icon: const FaIcon(FontAwesomeIcons.gear, size: 19), ), @@ -227,7 +209,7 @@ class _ChatListViewState extends State { child: isConnected ? Container() : const ConnectionInfo(), ), Positioned.fill( - child: (_contacts.isEmpty && _pinnedContacts.isEmpty) + child: (_groupsNotPinned.isEmpty && _groupsPinned.isEmpty) ? Center( child: Padding( padding: const EdgeInsets.all(10), @@ -252,9 +234,9 @@ class _ChatListViewState extends State { await Future.delayed(const Duration(seconds: 1)); }, child: ListView.builder( - itemCount: _pinnedContacts.length + - (_pinnedContacts.isNotEmpty ? 1 : 0) + - _contacts.length + + itemCount: _groupsPinned.length + + (_groupsPinned.isNotEmpty ? 1 : 0) + + _groupsNotPinned.length + 1, itemBuilder: (context, index) { if (index == 0) { @@ -262,11 +244,11 @@ class _ChatListViewState extends State { } index -= 1; // Check if the index is for the pinned users - if (index < _pinnedContacts.length) { - final contact = _pinnedContacts[index]; - return UserListItem( - key: ValueKey(contact.userId), - user: contact, + if (index < _groupsPinned.length) { + final group = _groupsPinned[index]; + return GroupListItem( + key: ValueKey(group.groupId), + group: group, firstUserListItemKey: (index == 0 || index == 1) ? firstUserListItemKey : null, @@ -274,21 +256,21 @@ class _ChatListViewState extends State { } // If there are pinned users, account for the Divider - var adjustedIndex = index - _pinnedContacts.length; - if (_pinnedContacts.isNotEmpty && adjustedIndex == 0) { + var adjustedIndex = index - _groupsPinned.length; + if (_groupsPinned.isNotEmpty && adjustedIndex == 0) { return const Divider(); } // Adjust the index for the contacts list - adjustedIndex -= (_pinnedContacts.isNotEmpty ? 1 : 0); + adjustedIndex -= (_groupsPinned.isNotEmpty ? 1 : 0); // Get the contacts that are not pinned - final contact = _contacts.elementAt( + final group = _groupsNotPinned.elementAt( adjustedIndex, ); - return UserListItem( - key: ValueKey(contact.userId), - user: contact, + return GroupListItem( + key: ValueKey(group.groupId), + group: group, firstUserListItemKey: (index == 0) ? firstUserListItemKey : null, ); @@ -317,219 +299,3 @@ class _ChatListViewState extends State { ); } } - -class UserListItem extends StatefulWidget { - const UserListItem({ - required this.user, - required this.firstUserListItemKey, - super.key, - }); - final Contact user; - final GlobalKey? firstUserListItemKey; - - @override - State createState() => _UserListItem(); -} - -class _UserListItem extends State { - MessageSendState state = MessageSendState.send; - Message? currentMessage; - - List messagesNotOpened = []; - late StreamSubscription> messagesNotOpenedStream; - - List lastMessages = []; - late StreamSubscription> lastMessageStream; - - List previewMessages = []; - bool hasNonOpenedMediaFile = false; - - @override - void initState() { - super.initState(); - initStreams(); - } - - @override - void dispose() { - messagesNotOpenedStream.cancel(); - lastMessageStream.cancel(); - super.dispose(); - } - - void initStreams() { - lastMessageStream = twonlyDB.messagesDao - .watchLastMessage(widget.user.userId) - .listen((update) { - updateState(update, messagesNotOpened); - }); - - messagesNotOpenedStream = twonlyDB.messagesDao - .watchMessageNotOpened(widget.user.userId) - .listen((update) { - updateState(lastMessages, update); - }); - } - - void updateState( - List newLastMessages, - List newMessagesNotOpened, - ) { - if (newLastMessages.isEmpty) { - // there are no messages at all - currentMessage = null; - previewMessages = []; - } else if (newMessagesNotOpened.isEmpty) { - // there are no not opened messages show just the last message in the table - currentMessage = newLastMessages.last; - previewMessages = newLastMessages; - } else { - // filter first for received messages - final receivedMessages = - newMessagesNotOpened.where((x) => x.messageOtherId != null).toList(); - - if (receivedMessages.isNotEmpty) { - previewMessages = receivedMessages; - currentMessage = receivedMessages.first; - } else { - previewMessages = newMessagesNotOpened; - currentMessage = newMessagesNotOpened.first; - } - } - - final msgs = - previewMessages.where((x) => x.kind == MessageKind.media).toList(); - if (msgs.isNotEmpty && - msgs.first.kind == MessageKind.media && - msgs.first.messageOtherId != null && - msgs.first.openedAt == null) { - hasNonOpenedMediaFile = true; - } else { - hasNonOpenedMediaFile = false; - } - - lastMessages = newLastMessages; - messagesNotOpened = newMessagesNotOpened; - setState(() { - // sets lastMessages, messagesNotOpened and currentMessage - }); - } - - Future onTap() async { - if (currentMessage == null) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return CameraSendToView(widget.user); - }, - ), - ); - return; - } - - if (hasNonOpenedMediaFile) { - final msgs = - previewMessages.where((x) => x.kind == MessageKind.media).toList(); - switch (msgs.first.downloadState) { - case DownloadState.pending: - await startDownloadMedia(msgs.first, true); - return; - case DownloadState.downloaded: - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return MediaViewerView(widget.user); - }, - ), - ); - return; - case DownloadState.downloading: - return; - } - } - if (!mounted) return; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ChatMessagesView(widget.user); - }, - ), - ); - } - - @override - Widget build(BuildContext context) { - final flameCounter = getFlameCounterFromContact(widget.user); - - return Stack( - children: [ - Positioned( - top: 0, - bottom: 0, - left: 50, - child: SizedBox( - key: widget.firstUserListItemKey, - height: 20, - width: 20, - ), - ), - UserContextMenu( - contact: widget.user, - child: ListTile( - title: Text( - getContactDisplayName(widget.user), - ), - subtitle: (widget.user.deleted) - ? Text(context.lang.userDeletedAccount) - : (currentMessage == null) - ? Text(context.lang.chatsTapToSend) - : Row( - children: [ - MessageSendStateIcon(previewMessages), - const Text('•'), - const SizedBox(width: 5), - if (currentMessage != null) - LastMessageTime(message: currentMessage!), - if (flameCounter > 0) - FlameCounterWidget( - widget.user, - flameCounter, - prefix: true, - ), - ], - ), - leading: ContactAvatar(contact: widget.user), - trailing: (widget.user.deleted) - ? null - : IconButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - if (hasNonOpenedMediaFile) { - return ChatMessagesView(widget.user); - } else { - return CameraSendToView(widget.user); - } - }, - ), - ); - }, - icon: FaIcon( - hasNonOpenedMediaFile - ? FontAwesomeIcons.solidComments - : FontAwesomeIcons.camera, - color: context.color.outline.withAlpha(150), - ), - ), - onTap: onTap, - ), - ), - ], - ); - } -} diff --git a/lib/src/views/chats/chat_list_components/group_list_item.dart b/lib/src/views/chats/chat_list_components/group_list_item.dart new file mode 100644 index 0000000..dbb2fa4 --- /dev/null +++ b/lib/src/views/chats/chat_list_components/group_list_item.dart @@ -0,0 +1,227 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; +import 'package:twonly/src/database/tables/messages.table.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/api/mediafiles/download.service.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/camera/camera_send_to_view.dart'; +import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart'; +import 'package:twonly/src/views/chats/chat_messages.view.dart'; +import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart'; +import 'package:twonly/src/views/chats/media_viewer.view.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; +import 'package:twonly/src/views/components/flame.dart'; +import 'package:twonly/src/views/components/group_context_menu.component.dart'; + +class GroupListItem extends StatefulWidget { + const GroupListItem({ + required this.group, + required this.firstUserListItemKey, + super.key, + }); + final Group group; + final GlobalKey? firstUserListItemKey; + + @override + State createState() => _UserListItem(); +} + +class _UserListItem extends State { + MessageSendState state = MessageSendState.send; + Message? currentMessage; + + List messagesNotOpened = []; + late StreamSubscription> messagesNotOpenedStream; + + List lastMessages = []; + late StreamSubscription> lastMessageStream; + + List previewMessages = []; + bool hasNonOpenedMediaFile = false; + + @override + void initState() { + super.initState(); + initStreams(); + } + + @override + void dispose() { + messagesNotOpenedStream.cancel(); + lastMessageStream.cancel(); + super.dispose(); + } + + void initStreams() { + lastMessageStream = twonlyDB.messagesDao + .watchLastMessage(widget.group.groupId) + .listen((update) { + updateState(update, messagesNotOpened); + }); + + messagesNotOpenedStream = twonlyDB.messagesDao + .watchMessageNotOpened(widget.group.groupId) + .listen((update) { + updateState(lastMessages, update); + }); + } + + void updateState( + List newLastMessages, + List newMessagesNotOpened, + ) { + if (newLastMessages.isEmpty) { + // there are no messages at all + currentMessage = null; + previewMessages = []; + } else if (newMessagesNotOpened.isEmpty) { + // there are no not opened messages show just the last message in the table + currentMessage = newLastMessages.last; + previewMessages = newLastMessages; + } else { + // filter first for received messages + final receivedMessages = + newMessagesNotOpened.where((x) => x.senderId != null).toList(); + + if (receivedMessages.isNotEmpty) { + previewMessages = receivedMessages; + currentMessage = receivedMessages.first; + } else { + previewMessages = newMessagesNotOpened; + currentMessage = newMessagesNotOpened.first; + } + } + + final msgs = + previewMessages.where((x) => x.type == MessageType.media).toList(); + if (msgs.isNotEmpty && + msgs.first.type == MessageType.media && + msgs.first.senderId != null && + msgs.first.openedAt == null) { + hasNonOpenedMediaFile = true; + } else { + hasNonOpenedMediaFile = false; + } + + lastMessages = newLastMessages; + messagesNotOpened = newMessagesNotOpened; + setState(() { + // sets lastMessages, messagesNotOpened and currentMessage + }); + } + + Future onTap() async { + if (currentMessage == null) { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return CameraSendToView(widget.group); + }, + ), + ); + return; + } + + if (hasNonOpenedMediaFile) { + final msgs = + previewMessages.where((x) => x.type == MessageType.media).toList(); + final mediaFile = + await twonlyDB.mediaFilesDao.getMediaFileById(msgs.first.mediaId!); + if (mediaFile?.downloadState == null) return; + if (mediaFile!.downloadState! == DownloadState.pending) { + await startDownloadMedia(mediaFile, true); + return; + } + if (mediaFile.downloadState! == DownloadState.downloaded) { + if (!mounted) return; + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return MediaViewerView(widget.group); + }, + ), + ); + return; + } + } + if (!mounted) return; + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return ChatMessagesView(widget.group); + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + top: 0, + bottom: 0, + left: 50, + child: SizedBox( + key: widget.firstUserListItemKey, + height: 20, + width: 20, + ), + ), + GroupContextMenu( + group: widget.group, + child: ListTile( + title: Text( + widget.group.groupName, + ), + subtitle: (currentMessage == null) + ? Text(context.lang.chatsTapToSend) + : Row( + children: [ + MessageSendStateIcon(previewMessages), + const Text('•'), + const SizedBox(width: 5), + if (currentMessage != null) + LastMessageTime(message: currentMessage!), + FlameCounterWidget( + groupId: widget.group.groupId, + prefix: true, + ), + ], + ), + leading: AvatarIcon(group: widget.group), + trailing: IconButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + if (hasNonOpenedMediaFile) { + return ChatMessagesView(widget.group); + } else { + return CameraSendToView(widget.group); + } + }, + ), + ); + }, + icon: FaIcon( + hasNonOpenedMediaFile + ? FontAwesomeIcons.solidComments + : FontAwesomeIcons.camera, + color: context.color.outline.withAlpha(150), + ), + ), + onTap: onTap, + ), + ), + ], + ); + } +} diff --git a/lib/src/views/chats/chat_list_components/last_message_time.dart b/lib/src/views/chats/chat_list_components/last_message_time.dart index 273869c..b81981a 100644 --- a/lib/src/views/chats/chat_list_components/last_message_time.dart +++ b/lib/src/views/chats/chat_list_components/last_message_time.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -21,10 +22,13 @@ class _LastMessageTimeState extends State { void initState() { super.initState(); // Change the color every 200 milliseconds - updateTime = Timer.periodic(const Duration(milliseconds: 500), (timer) { + updateTime = + Timer.periodic(const Duration(milliseconds: 500), (timer) async { + final lastAction = await twonlyDB.messagesDao + .getLastMessageAction(widget.message.messageId); setState(() { lastMessageInSeconds = DateTime.now() - .difference(widget.message.openedAt ?? widget.message.sendAt) + .difference(lastAction?.actionAt ?? widget.message.createdAt) .inSeconds; if (lastMessageInSeconds < 0) { lastMessageInSeconds = 0; diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index 603c4cb..a318f43 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -1,19 +1,13 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:convert'; -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:pie_menu/pie_menu.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/daos/contacts.dao.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.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; import 'package:twonly/src/model/memory_item.model.dart'; -import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -21,25 +15,17 @@ import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/chats/chat_messages_components/chat_date_chip.dart'; import 'package:twonly/src/views/chats/chat_messages_components/chat_list_entry.dart'; import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart'; -import 'package:twonly/src/views/components/animate_icon.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; -import 'package:twonly/src/views/components/user_context_menu.component.dart'; -import 'package:twonly/src/views/components/verified_shield.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/contact/contact.view.dart'; +import 'package:twonly/src/views/groups/group.view.dart'; import 'package:twonly/src/views/tutorial/tutorials.dart'; Color getMessageColor(Message message) { - return (message.messageOtherId == null) + return (message.senderId == null) ? const Color.fromARGB(255, 58, 136, 102) : const Color.fromARGB(233, 68, 137, 255); } -class ChatMessage { - ChatMessage({required this.message, required this.responseTo}); - final Message message; - final Message? responseTo; -} - class ChatItem { const ChatItem._({this.message, this.date, this.time}); factory ChatItem.date(DateTime date) { @@ -48,10 +34,10 @@ class ChatItem { factory ChatItem.time(DateTime time) { return ChatItem._(time: time); } - factory ChatItem.message(ChatMessage message) { + factory ChatItem.message(Message message) { return ChatItem._(message: message); } - final ChatMessage? message; + final Message? message; final DateTime? date; final DateTime? time; bool get isMessage => message != null; @@ -72,14 +58,13 @@ class ChatMessagesView extends StatefulWidget { class _ChatMessagesViewState extends State { TextEditingController newMessageController = TextEditingController(); HashSet alreadyReportedOpened = HashSet(); - late Contact user; + late Group group; String currentInputText = ''; - late StreamSubscription userSub; + late StreamSubscription userSub; late StreamSubscription> messageSub; List messages = []; List galleryItems = []; - Map> emojiReactionsToMessageId = {}; - Message? responseToMessage; + Message? quotesMessage; GlobalKey verifyShieldKey = GlobalKey(); late FocusNode textFieldFocus; Timer? tutorial; @@ -89,7 +74,7 @@ class _ChatMessagesViewState extends State { @override void initState() { super.initState(); - user = widget.contact; + group = widget.group; textFieldFocus = FocusNode(); initStreams(); @@ -110,118 +95,59 @@ class _ChatMessagesViewState extends State { } Future initStreams() async { - await twonlyDB.messagesDao.removeOldMessages(); - final contact = twonlyDB.contactsDao.watchContact(widget.contact.userId); - userSub = contact.listen((contact) { - if (contact == null) return; + final groupStream = twonlyDB.groupsDao.watchGroup(group.groupId); + userSub = groupStream.listen((newGroup) { + if (newGroup == null) return; setState(() { - user = contact; + group = newGroup; }); }); - final msgStream = - twonlyDB.messagesDao.watchAllMessagesFrom(widget.contact.userId); + final msgStream = twonlyDB.messagesDao.watchByGroupId(group.groupId); messageSub = msgStream.listen((newMessages) async { - // if (!context.mounted) return; - if (Platform.isAndroid) { - await flutterLocalNotificationsPlugin.cancel(widget.contact.userId); - } else { - await flutterLocalNotificationsPlugin.cancelAll(); - } + await flutterLocalNotificationsPlugin.cancelAll(); + final chatItems = []; final storedMediaFiles = []; + DateTime? lastDate; - final tmpEmojiReactionsToMessageId = >{}; - // only send openedMessage to one text message, as receiver will then set all as read... - List openedTextMessageOtherIds; - - final messageOtherMessageIdToMyMessageId = {}; - final messageIdToMessage = {}; - - /// there is probably a better way... - for (final msg in newMessages) { - if (msg.messageOtherId != null) { - messageOtherMessageIdToMyMessageId[msg.messageOtherId!] = - msg.messageId; - } - messageIdToMessage[msg.messageId] = msg; - } + final openedMessages = >{}; for (final msg in newMessages) { - if (msg.kind == MessageKind.textMessage && - msg.messageOtherId != null && - msg.openedAt == null && - (openedTextMessageOtherIds == null || - openedTextMessageOtherIds < msg.messageOtherId!)) { - openedTextMessageOtherIds.add(msg.messageOtherId); + if (msg.type == MessageType.text && + msg.senderId != null && + msg.openedAt == null) { + openedMessages[msg.senderId!]!.add(msg.messageId); } - Message? responseTo; - - if (msg.kind == MessageKind.media && msg.mediaStored) { + if (msg.type == MessageType.media && msg.mediaStored) { storedMediaFiles.add(msg); } - final responseId = msg.responseToMessageId ?? - messageOtherMessageIdToMyMessageId[msg.responseToOtherMessageId]; - - var isReaction = false; - if (responseId != null) { - responseTo = messageIdToMessage[responseId]; - final content = MessageContent.fromJson( - msg.kind, - jsonDecode(msg.contentJson!) as Map, - ); - if (content is TextMessageContent) { - if (isEmoji(content.text)) { - isReaction = true; - tmpEmojiReactionsToMessageId - .putIfAbsent(responseId, () => []) - .add(msg); - } - } - if (msg.kind == MessageKind.reopenedMedia) { - isReaction = true; - tmpEmojiReactionsToMessageId - .putIfAbsent(responseId, () => []) - .add(msg); - } - } - if (!isReaction) { - if (lastDate == null || - msg.sendAt.day != lastDate.day || - msg.sendAt.month != lastDate.month || - msg.sendAt.year != lastDate.year) { - chatItems.add(ChatItem.date(msg.sendAt)); - lastDate = msg.sendAt; - } else if (msg.sendAt.difference(lastDate).inMinutes >= 20) { - chatItems.add(ChatItem.time(msg.sendAt)); - lastDate = msg.sendAt; - } - chatItems.add( - ChatItem.message( - ChatMessage( - message: msg, - responseTo: responseTo, - ), - ), - ); + if (lastDate == null || + msg.createdAt.day != lastDate.day || + msg.createdAt.month != lastDate.month || + msg.createdAt.year != lastDate.year) { + chatItems.add(ChatItem.date(msg.createdAt)); + lastDate = msg.createdAt; + } else if (msg.createdAt.difference(lastDate).inMinutes >= 20) { + chatItems.add(ChatItem.time(msg.createdAt)); + lastDate = msg.createdAt; } + chatItems.add(ChatItem.message(msg)); } - if (openedTextMessageOtherIds.isNotEmpty) { + for (final contactId in openedMessages.keys) { await notifyContactAboutOpeningMessage( - widget.contact.userId, - openedTextMessageOtherIds, + contactId, + openedMessages[contactId]!, ); } - await twonlyDB.messagesDao - .openedAllNonMediaMessages(widget.contact.userId); + await twonlyDB.messagesDao.openedAllTextMessages(widget.group.groupId); setState(() { - emojiReactionsToMessageId = tmpEmojiReactionsToMessageId; messages = chatItems.reversed.toList(); }); @@ -234,33 +160,21 @@ class _ChatMessagesViewState extends State { Future _sendMessage() async { if (newMessageController.text == '') return; - await sendTextMessage( - user.userId, - TextMessageContent( - text: newMessageController.text, - responseToMessageId: responseToMessage?.messageOtherId, - responseToOtherMessageId: responseToMessage?.messageId, - ), - PushNotification( - kind: (responseToMessage == null) - ? PushKind.text - : (isEmoji(newMessageController.text)) - ? PushKind.reaction - : PushKind.response, - reactionContent: (isEmoji(newMessageController.text)) - ? newMessageController.text - : null, - ), + await insertAndSendTextMessage( + group.groupId, + newMessageController.text, + quotesMessage?.messageId, ); + newMessageController.clear(); currentInputText = ''; - responseToMessage = null; + quotesMessage = null; setState(() {}); } - Future scrollToMessage(int messageId) async { + Future scrollToMessage(String messageId) async { final index = messages.indexWhere( - (x) => x.isMessage && x.message!.message.messageId == messageId, + (x) => x.isMessage && x.message!.messageId == messageId, ); if (index == -1) return; setState(() { @@ -286,20 +200,34 @@ class _ChatMessagesViewState extends State { child: Scaffold( appBar: AppBar( title: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ContactView(widget.contact.userId); - }, - ), - ); + onTap: () async { + if (widget.group.isDirectChat) { + final member = await twonlyDB.groupsDao + .getGroupMembers(widget.group.groupId); + if (!context.mounted) return; + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return ContactView(member.first.contactId); + }, + ), + ); + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return GroupView(widget.group); + }, + ), + ); + } }, child: Row( children: [ - ContactAvatar( - contact: user, + AvatarIcon( + group: group, fontSize: 19, ), const SizedBox(width: 10), @@ -308,10 +236,10 @@ class _ChatMessagesViewState extends State { color: Colors.transparent, child: Row( children: [ - Text(getContactDisplayName(user)), + Text(group.groupName), const SizedBox(width: 10), - if (user.verified) - VerifiedShield(key: verifyShieldKey, user), + // if (group.verified) + // VerifiedShield(key: verifyShieldKey, group), ], ), ), @@ -345,7 +273,7 @@ class _ChatMessagesViewState extends State { return Transform.translate( offset: Offset( (focusedScrollItem == i) - ? (chatMessage.message.messageOtherId == null) + ? (chatMessage.quotesMessageId == null) ? -8 : 8 : 0, @@ -354,19 +282,15 @@ class _ChatMessagesViewState extends State { child: Transform.scale( scale: (focusedScrollItem == i) ? 1.05 : 1, child: ChatListEntry( - key: - Key(chatMessage.message.messageId.toString()), + key: Key(chatMessage.messageId), chatMessage, - user, + group, galleryItems, isLastMessageFromSameUser(messages, i), - emojiReactionsToMessageId[ - chatMessage.message.messageId] ?? - [], scrollToMessage: scrollToMessage, onResponseTriggered: () { setState(() { - responseToMessage = chatMessage.message; + quotesMessage = chatMessage; }); textFieldFocus.requestFocus(); }, @@ -377,7 +301,7 @@ class _ChatMessagesViewState extends State { }, ), ), - if (responseToMessage != null && !user.deleted) + if (quotesMessage != null) Container( padding: const EdgeInsets.only( left: 20, @@ -388,15 +312,15 @@ class _ChatMessagesViewState extends State { children: [ Expanded( child: ResponsePreview( - message: responseToMessage!, + message: quotesMessage, showBorder: true, - contact: user, + group: group, ), ), IconButton( onPressed: () { setState(() { - responseToMessage = null; + quotesMessage = null; }); }, icon: const FaIcon( @@ -415,50 +339,48 @@ class _ChatMessagesViewState extends State { top: 10, ), child: Row( - children: (user.deleted) - ? [] - : [ - Expanded( - child: TextField( - controller: newMessageController, - focusNode: textFieldFocus, - keyboardType: TextInputType.multiline, - maxLines: 4, - minLines: 1, - onChanged: (value) { - currentInputText = value; - setState(() {}); - }, - onSubmitted: (_) { - _sendMessage(); - }, - decoration: inputTextMessageDeco(context), - ), - ), - if (currentInputText != '') - IconButton( - padding: const EdgeInsets.all(15), - icon: const FaIcon( - FontAwesomeIcons.solidPaperPlane, - ), - onPressed: _sendMessage, - ) - else - IconButton( - icon: const FaIcon(FontAwesomeIcons.camera), - padding: const EdgeInsets.all(15), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return CameraSendToView(widget.contact); - }, - ), - ); + children: [ + Expanded( + child: TextField( + controller: newMessageController, + focusNode: textFieldFocus, + keyboardType: TextInputType.multiline, + maxLines: 4, + minLines: 1, + onChanged: (value) { + currentInputText = value; + setState(() {}); + }, + onSubmitted: (_) { + _sendMessage(); + }, + decoration: inputTextMessageDeco(context), + ), + ), + if (currentInputText != '') + IconButton( + padding: const EdgeInsets.all(15), + icon: const FaIcon( + FontAwesomeIcons.solidPaperPlane, + ), + onPressed: _sendMessage, + ) + else + IconButton( + icon: const FaIcon(FontAwesomeIcons.camera), + padding: const EdgeInsets.all(15), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return CameraSendToView(widget.group); }, ), - ], + ); + }, + ), + ], ), ), ], @@ -479,11 +401,11 @@ bool isLastMessageFromSameUser(List messages, int index) { final currentMessage = messages[index]; if (lastMessage.isMessage && currentMessage.isMessage) { - // Check if both messages have the same messageOtherId (or both are null) - return (lastMessage.message!.message.messageOtherId == null && - currentMessage.message!.message.messageOtherId == null) || - (lastMessage.message!.message.messageOtherId != null && - currentMessage.message!.message.messageOtherId != null); + // Check if both messages have the same quotesMessageId (or both are null) + return (lastMessage.message!.quotesMessageId == null && + currentMessage.message!.quotesMessageId == null) || + (lastMessage.message!.quotesMessageId != null && + currentMessage.message!.quotesMessageId != null); } return false; } diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart index d4770b5..990f127 100644 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart @@ -1,10 +1,9 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; +import 'package:twonly/src/database/tables/messages.table.dart' + hide MessageActions; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; import 'package:twonly/src/model/memory_item.model.dart'; -import 'package:twonly/src/views/chats/chat_messages.view.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/views/chats/chat_messages_components/chat_media_entry.dart'; import 'package:twonly/src/views/chats/chat_messages_components/chat_reaction_row.dart'; import 'package:twonly/src/views/chats/chat_messages_components/chat_text_entry.dart'; @@ -14,21 +13,19 @@ import 'package:twonly/src/views/chats/chat_messages_components/response_contain class ChatListEntry extends StatefulWidget { const ChatListEntry( - this.msg, - this.contact, + this.message, + this.group, this.galleryItems, - this.lastMessageFromSameUser, - this.otherReactions, { + this.lastMessageFromSameUser, { required this.onResponseTriggered, required this.scrollToMessage, super.key, }); - final ChatMessage msg; - final Contact contact; + final Message message; + final Group group; final bool lastMessageFromSameUser; - final List otherReactions; final List galleryItems; - final void Function(int) scrollToMessage; + final void Function(String) scrollToMessage; final void Function() onResponseTriggered; @override @@ -36,26 +33,22 @@ class ChatListEntry extends StatefulWidget { } class _ChatListEntryState extends State { - MessageContent? content; - String? textMessage; + MediaFileService? mediaService; @override void initState() { + initAsync(); super.initState(); - final msgContent = MessageContent.fromJson( - widget.msg.message.kind, - jsonDecode(widget.msg.message.contentJson!) as Map, - ); - if (msgContent is TextMessageContent) { - textMessage = msgContent.text; - } - content = msgContent; + } + + Future initAsync() async { + mediaService = await MediaFileService.fromMediaId(widget.message.messageId); + setState(() {}); } @override Widget build(BuildContext context) { - if (content == null) return Container(); - final right = widget.msg.message.messageOtherId == null; + final right = widget.message.senderId == null; return Align( alignment: right ? Alignment.centerRight : Alignment.centerLeft, @@ -64,7 +57,7 @@ class _ChatListEntryState extends State { ? const EdgeInsets.only(top: 5, right: 10, left: 10) : const EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10), child: MessageContextMenu( - message: widget.msg.message, + message: widget.message, onResponseTriggered: widget.onResponseTriggered, child: Column( mainAxisAlignment: @@ -73,36 +66,36 @@ class _ChatListEntryState extends State { right ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ MessageActions( - message: widget.msg.message, + message: widget.message, onResponseTriggered: widget.onResponseTriggered, child: Stack( alignment: right ? Alignment.centerRight : Alignment.centerLeft, children: [ ResponseContainer( - msg: widget.msg, - contact: widget.contact, + msg: widget.message, + group: widget.group, + mediaService: mediaService, scrollToMessage: widget.scrollToMessage, - child: (textMessage != null) + child: (widget.message.type == MessageType.text) ? ChatTextEntry( - message: widget.msg, - text: textMessage!, - hasReaction: widget.otherReactions.isNotEmpty, + message: widget.message, ) - : ChatMediaEntry( - message: widget.msg.message, - contact: widget.contact, - galleryItems: widget.galleryItems, - content: content!, - ), + : (mediaService == null) + ? null + : ChatMediaEntry( + message: widget.message, + group: widget.group, + mediaService: mediaService!, + galleryItems: widget.galleryItems, + ), ), Positioned( bottom: 5, left: 5, right: 5, child: ReactionRow( - otherReactions: widget.otherReactions, - message: widget.msg.message, + message: widget.message, ), ), ], diff --git a/lib/src/views/chats/chat_messages_components/chat_media_entry.dart b/lib/src/views/chats/chat_messages_components/chat_media_entry.dart index 4be5049..32fa297 100644 --- a/lib/src/views/chats/chat_messages_components/chat_media_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_media_entry.dart @@ -1,35 +1,33 @@ import 'dart:async'; - -import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/tables/messages_table.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; +import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; import 'package:twonly/src/model/memory_item.model.dart'; -import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' + hide Message; import 'package:twonly/src/services/api/mediafiles/download.service.dart' as received; import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_editor_view.dart'; import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart'; import 'package:twonly/src/views/chats/media_viewer.view.dart'; -import 'package:twonly/src/views/tutorial/tutorials.dart'; class ChatMediaEntry extends StatefulWidget { const ChatMediaEntry({ required this.message, - required this.contact, - required this.content, + required this.group, required this.galleryItems, + required this.mediaService, super.key, }); final Message message; - final Contact contact; - final MessageContent content; + final Group group; final List galleryItems; + final MediaFileService mediaService; @override State createState() => _ChatMediaEntryState(); @@ -39,97 +37,58 @@ class _ChatMediaEntryState extends State { GlobalKey reopenMediaFile = GlobalKey(); bool canBeReopened = false; - @override - void initState() { - super.initState(); - unawaited(checkIfTutorialCanBeShown()); - } - - Future checkIfTutorialCanBeShown() async { - if (widget.message.openedAt == null && - widget.message.messageOtherId != null || - widget.message.mediaStored) { - return; - } - final content = getMediaContent(widget.message); - if (content == null || - content.isRealTwonly || - content.maxShowTime != gMediaShowInfinite) { - return; - } - if (await received.existsMediaFile(widget.message.messageId, 'png')) { - if (mounted) { - setState(() { - canBeReopened = true; - }); - } - Future.delayed(const Duration(seconds: 1), () async { - if (!mounted) return; - await showReopenMediaFilesTutorial(context, reopenMediaFile); - }); - } - } - Future onDoubleTap() async { - if (widget.message.openedAt == null && - widget.message.messageOtherId != null || - widget.message.mediaStored) { + if (widget.message.openedAt == null || widget.message.mediaStored) { return; } - if (await received.existsMediaFile(widget.message.messageId, 'png')) { - await encryptAndSendMessageAsync( - null, - widget.contact.userId, - MessageJson( - kind: MessageKind.reopenedMedia, - messageSenderId: widget.message.messageId, - content: ReopenedMediaFileContent( - messageId: widget.message.messageOtherId!, + if (widget.mediaService.tempPath.existsSync()) { + await sendCipherTextToGroup( + widget.message.groupId, + EncryptedContent( + mediaUpdate: EncryptedContent_MediaUpdate( + type: EncryptedContent_MediaUpdate_Type.REOPENED, + targetMediaId: widget.message.mediaId, ), - timestamp: DateTime.now(), - ), - pushNotification: PushNotification( - kind: PushKind.reopenedMedia, ), ); - await twonlyDB.messagesDao.updateMessageByMessageId( - widget.message.messageId, - const MessagesCompanion(openedAt: Value(null)), - ); + await twonlyDB.messagesDao.reopenedMedia(widget.message.messageId); } } Future onTap() async { - if (widget.message.downloadState == DownloadState.downloaded && + if (widget.mediaService.mediaFile.downloadState == + DownloadState.downloaded && widget.message.openedAt == null) { + if (!mounted) return; await Navigator.push( context, MaterialPageRoute( builder: (context) { return MediaViewerView( - widget.contact, + widget.group, initialMessage: widget.message, ); }, ), ); - await checkIfTutorialCanBeShown(); - } else if (widget.message.downloadState == DownloadState.pending) { - await received.startDownloadMedia(widget.message, true); + } else if (widget.mediaService.mediaFile.downloadState == + DownloadState.pending) { + await received.startDownloadMedia(widget.mediaService.mediaFile, true); } } @override Widget build(BuildContext context) { final color = getMessageColorFromType( - widget.content, + widget.message, + widget.mediaService.mediaFile, context, ); return GestureDetector( key: reopenMediaFile, onDoubleTap: onDoubleTap, - onTap: widget.message.kind == MessageKind.media ? onTap : null, + onTap: (widget.message.type == MessageType.media) ? onTap : null, child: SizedBox( width: 150, height: widget.message.mediaStored ? 271 : null, @@ -139,7 +98,8 @@ class _ChatMediaEntryState extends State { borderRadius: BorderRadius.circular(12), child: InChatMediaViewer( message: widget.message, - contact: widget.contact, + group: widget.group, + mediaService: widget.mediaService, color: color, galleryItems: widget.galleryItems, canBeReopened: canBeReopened, diff --git a/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart b/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart index d859e8f..8f95acb 100644 --- a/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart +++ b/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart @@ -1,20 +1,15 @@ -import 'dart:convert'; - +import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; -import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; class ReactionRow extends StatefulWidget { const ReactionRow({ - required this.otherReactions, required this.message, super.key, }); - final List otherReactions; final Message message; @override @@ -22,65 +17,79 @@ class ReactionRow extends StatefulWidget { } class _ReactionRowState extends State { + List reactions = []; + StreamSubscription>? reactionsSub; + + @override + void initState() { + initAsync(); + super.initState(); + } + + @override + void dispose() { + reactionsSub?.cancel(); + super.dispose(); + } + + Future initAsync() async { + final stream = + twonlyDB.reactionsDao.watchReactions(widget.message.messageId); + + reactionsSub = stream.listen((update) { + setState(() { + reactions = update; + }); + }); + } + @override Widget build(BuildContext context) { final children = []; - var hasOneTextReaction = false; - var hasOneReopened = false; - for (final reaction in widget.otherReactions.reversed) { - final content = MessageContent.fromJson( - reaction.kind, - jsonDecode(reaction.contentJson!) as Map, - ); - - if (content is ReopenedMediaFileContent) { - if (hasOneReopened) continue; - hasOneReopened = true; - children.add( - Expanded( - child: Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.only(right: 3), - child: FaIcon( - FontAwesomeIcons.repeat, - size: 12, - color: isDarkMode(context) ? Colors.white : Colors.black, - ), - ), - ), - ), - ); - } + for (final reaction in reactions) { + // if (content is ReopenedMediaFileContent) { + // if (hasOneReopened) continue; + // hasOneReopened = true; + // children.add( + // Expanded( + // child: Align( + // alignment: Alignment.bottomRight, + // child: Padding( + // padding: const EdgeInsets.only(right: 3), + // child: FaIcon( + // FontAwesomeIcons.repeat, + // size: 12, + // color: isDarkMode(context) ? Colors.white : Colors.black, + // ), + // ), + // ), + // ), + // ); + // } // only show one reaction - if (hasOneTextReaction) continue; - if (content is TextMessageContent) { - hasOneTextReaction = true; - if (!isEmoji(content.text)) continue; - late Widget child; - if (EmojiAnimation.animatedIcons.containsKey(content.text)) { - child = SizedBox( - height: 18, - child: EmojiAnimation(emoji: content.text), - ); - } else { - child = Text(content.text, style: const TextStyle(fontSize: 14)); - } - children.insert( - 0, - Padding( - padding: const EdgeInsets.only(left: 3), - child: child, - ), + late Widget child; + if (EmojiAnimation.animatedIcons.containsKey(reaction.emoji)) { + child = SizedBox( + height: 18, + child: EmojiAnimation(emoji: reaction.emoji), ); + } else { + child = Text(reaction.emoji, style: const TextStyle(fontSize: 14)); } + children.insert( + 0, + Padding( + padding: const EdgeInsets.only(left: 3), + child: child, + ), + ); } if (children.isEmpty) return Container(); return Row( - mainAxisAlignment: widget.message.messageOtherId == null + mainAxisAlignment: widget.message.senderId == null ? MainAxisAlignment.end : MainAxisAlignment.end, children: children, diff --git a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart index f161c52..c730270 100644 --- a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/better_text.dart'; @@ -6,17 +7,14 @@ import 'package:twonly/src/views/components/better_text.dart'; class ChatTextEntry extends StatelessWidget { const ChatTextEntry({ required this.message, - required this.text, - required this.hasReaction, super.key, }); - final String text; - final ChatMessage message; - final bool hasReaction; + final Message message; @override Widget build(BuildContext context) { + final text = message.content ?? ''; if (EmojiAnimation.supported(text)) { return Container( constraints: const BoxConstraints( @@ -33,16 +31,10 @@ class ChatTextEntry extends StatelessWidget { constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.8, ), - padding: EdgeInsets.only( - left: 10, - top: 4, - bottom: 4, - right: hasReaction ? 30 : 10, - ), + padding: const EdgeInsets.only(left: 10, top: 4, bottom: 4), decoration: BoxDecoration( - color: message.responseTo == null - ? getMessageColor(message.message) - : null, + color: + message.quotesMessageId == null ? getMessageColor(message) : null, borderRadius: BorderRadius.circular(12), ), child: BetterText(text: text), diff --git a/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart b/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart index 28a4623..255cac9 100644 --- a/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart +++ b/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart @@ -1,9 +1,9 @@ import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/memory_item.model.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart'; import 'package:twonly/src/views/memories/memories_item_thumbnail.dart'; import 'package:twonly/src/views/memories/memories_photo_slider.view.dart'; @@ -11,7 +11,8 @@ import 'package:twonly/src/views/memories/memories_photo_slider.view.dart'; class InChatMediaViewer extends StatefulWidget { const InChatMediaViewer({ required this.message, - required this.contact, + required this.group, + required this.mediaService, required this.color, required this.galleryItems, required this.canBeReopened, @@ -19,7 +20,8 @@ class InChatMediaViewer extends StatefulWidget { }); final Message message; - final Contact contact; + final Group group; + final MediaFileService mediaService; final List galleryItems; final Color color; final bool canBeReopened; @@ -56,8 +58,7 @@ class _InChatMediaViewerState extends State { bool loadIndex() { if (widget.message.mediaStored) { final index = widget.galleryItems.indexWhere( - (x) => - x.id == (widget.message.mediaUploadId ?? widget.message.messageId), + (x) => x.mediaService.mediaFile.mediaId == (widget.message.messageId), ); if (index != -1) { galleryItemIndex = index; @@ -83,7 +84,7 @@ class _InChatMediaViewerState extends State { if (widget.message.mediaStored) return; final stream = twonlyDB.messagesDao - .getMessageByMessageId(widget.message.messageId) + .getMessageById(widget.message.messageId) .watchSingleOrNull(); messageStream = stream.listen((updated) async { if (updated != null) { diff --git a/lib/src/views/chats/chat_messages_components/message_context_menu.dart b/lib/src/views/chats/chat_messages_components/message_context_menu.dart index 4118d69..542fcdd 100644 --- a/lib/src/views/chats/chat_messages_components/message_context_menu.dart +++ b/lib/src/views/chats/chat_messages_components/message_context_menu.dart @@ -1,14 +1,14 @@ // ignore_for_file: inference_failure_on_function_invocation +import 'package:drift/drift.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:pie_menu/pie_menu.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; -import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart' + as pb; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; @@ -48,24 +48,22 @@ class MessageContextMenu extends StatelessWidget { ) as EmojiLayerData?; if (layer == null) return; - await sendTextMessage( - message.contactId, - TextMessageContent( - text: layer.text, - responseToMessageId: message.messageOtherId, - responseToOtherMessageId: - (message.messageOtherId == null) ? message.messageId : null, + await twonlyDB.reactionsDao.insertReaction( + ReactionsCompanion( + messageId: Value(message.messageId), + emoji: Value(layer.text), + ), + ); + + await sendCipherTextToGroup( + message.groupId, + pb.EncryptedContent( + reaction: pb.EncryptedContent_Reaction( + targetMessageId: message.messageId, + emoji: layer.text, + remove: false, + ), ), - (message.messageOtherId != null) - ? PushNotification( - kind: (message.kind == MessageKind.textMessage) - ? PushKind.reactionToText - : (getMediaContent(message)!.isVideo) - ? PushKind.reactionToVideo - : PushKind.reactionToImage, - reactionContent: layer.text, - ) - : null, ); }, child: const FaIcon(FontAwesomeIcons.faceLaugh), @@ -75,15 +73,15 @@ class MessageContextMenu extends StatelessWidget { onSelect: onResponseTriggered, child: const FaIcon(FontAwesomeIcons.reply), ), - PieAction( - tooltip: Text(context.lang.copy), - onSelect: () async { - final text = getMessageText(message); - await Clipboard.setData(ClipboardData(text: text)); - await HapticFeedback.heavyImpact(); - }, - child: const FaIcon(FontAwesomeIcons.solidCopy), - ), + if (message.content != null) + PieAction( + tooltip: Text(context.lang.copy), + onSelect: () async { + await Clipboard.setData(ClipboardData(text: message.content!)); + await HapticFeedback.heavyImpact(); + }, + child: const FaIcon(FontAwesomeIcons.solidCopy), + ), PieAction( tooltip: Text(context.lang.delete), onSelect: () async { @@ -94,8 +92,7 @@ class MessageContextMenu extends StatelessWidget { customOk: context.lang.deleteOkBtn, ); if (delete) { - await twonlyDB.messagesDao - .deleteMessagesByMessageId(message.messageId); + await twonlyDB.messagesDao.deleteMessagesById(message.messageId); } }, child: const FaIcon(FontAwesomeIcons.trash), diff --git a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart index 52293e4..d423c10 100644 --- a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart +++ b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart @@ -1,11 +1,11 @@ import 'dart:collection'; -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/src/database/tables/messages_table.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; +import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; @@ -18,17 +18,23 @@ enum MessageSendState { sending, } -MessageSendState messageSendStateFromMessage(Message msg) { +Future messageSendStateFromMessage(Message msg) async { MessageSendState state; - if (!msg.acknowledgeByServer) { - if (msg.messageOtherId == null) { + final ackByServer = await twonlyDB.messagesDao.haveAllMembers( + msg.groupId, + msg.messageId, + MessageActionType.ackByServerAt, + ); + + if (!ackByServer) { + if (msg.senderId == null) { state = MessageSendState.sending; } else { state = MessageSendState.receiving; } } else { - if (msg.messageOtherId == null) { + if (msg.senderId == null) { // message send if (msg.openedAt == null) { state = MessageSendState.send; @@ -63,9 +69,113 @@ class MessageSendStateIcon extends StatefulWidget { } class _MessageSendStateIconState extends State { + List icons = []; + String text = ''; + Widget? textWidget; + @override void initState() { super.initState(); + initAsync(); + } + + Future initAsync() async { + final kindsAlreadyShown = HashSet(); + + for (final message in widget.messages) { + if (icons.length == 2) break; + if (kindsAlreadyShown.contains(message.type)) continue; + kindsAlreadyShown.add(message.type); + + final state = await messageSendStateFromMessage(message); + + final mediaFile = message.mediaId == null + ? null + : await MediaFileService.fromMediaId(message.mediaId!); + + if (!mounted) return; + + final color = + getMessageColorFromType(message, mediaFile?.mediaFile, context); + + Widget icon = const Placeholder(); + textWidget = null; + + switch (state) { + case MessageSendState.receivedOpened: + icon = Icon(Icons.crop_square, size: 14, color: color); + if (message.content != null) { + if (isEmoji(message.content!)) { + icon = Text( + message.content!, + style: const TextStyle(fontSize: 12), + ); + } + } + text = context.lang.messageSendState_Received; + if (widget.canBeReopened) { + textWidget = Text( + context.lang.doubleClickToReopen, + style: const TextStyle(fontSize: 9), + ); + } + case MessageSendState.sendOpened: + icon = FaIcon(FontAwesomeIcons.paperPlane, size: 12, color: color); + text = context.lang.messageSendState_Opened; + case MessageSendState.received: + icon = Icon(Icons.square_rounded, size: 14, color: color); + text = context.lang.messageSendState_Received; + if (message.type == MessageType.media) { + if (mediaFile!.mediaFile.downloadState == DownloadState.pending) { + text = context.lang.messageSendState_TapToLoad; + } + if (mediaFile.mediaFile.downloadState == + DownloadState.downloading) { + text = context.lang.messageSendState_Loading; + icon = getLoaderIcon(color); + } + } + case MessageSendState.send: + icon = + FaIcon(FontAwesomeIcons.solidPaperPlane, size: 12, color: color); + text = context.lang.messageSendState_Send; + case MessageSendState.sending: + icon = getLoaderIcon(color); + text = context.lang.messageSendState_Sending; + case MessageSendState.receiving: + icon = getLoaderIcon(color); + text = context.lang.messageSendState_Received; + } + + if (message.mediaStored) { + icon = FaIcon(FontAwesomeIcons.floppyDisk, size: 12, color: color); + text = context.lang.messageStoredInGallery; + } + + if (mediaFile != null) { + if (mediaFile.mediaFile.stored) { + icon = FaIcon(FontAwesomeIcons.repeat, size: 12, color: color); + text = context.lang.messageReopened; + } + + if (mediaFile.mediaFile.reuploadRequestedBy != null) { + icon = + FaIcon(FontAwesomeIcons.clockRotateLeft, size: 12, color: color); + textWidget = Text( + context.lang.retransmissionRequested, + style: const TextStyle(fontSize: 9), + ); + } + } + + if (message.type == MessageType.media) { + icons.insert(0, icon); + } else { + icons.add(icon); + } + } + + setState(() {}); } Widget getLoaderIcon(Color color) { @@ -83,108 +193,6 @@ class _MessageSendStateIconState extends State { @override Widget build(BuildContext context) { - final icons = []; - var text = ''; - - final kindsAlreadyShown = HashSet(); - Widget? textWidget; - - for (final message in widget.messages) { - if (icons.length == 2) break; - if (kindsAlreadyShown.contains(message.kind)) continue; - kindsAlreadyShown.add(message.kind); - - final state = messageSendStateFromMessage(message); - late Color color; - MessageContent? content; - - if (message.contentJson == null) { - color = getMessageColorFromType(TextMessageContent(text: ''), context); - } else { - content = MessageContent.fromJson( - message.kind, - jsonDecode(message.contentJson!) as Map, - ); - if (content == null) continue; - color = getMessageColorFromType(content, context); - } - - Widget icon = const Placeholder(); - textWidget = null; - - switch (state) { - case MessageSendState.receivedOpened: - icon = Icon(Icons.crop_square, size: 14, color: color); - if (content is TextMessageContent) { - if (isEmoji(content.text)) { - icon = Text(content.text, style: const TextStyle(fontSize: 12)); - } - } - text = context.lang.messageSendState_Received; - if (widget.canBeReopened) { - textWidget = Text( - context.lang.doubleClickToReopen, - style: const TextStyle(fontSize: 9), - ); - } - case MessageSendState.sendOpened: - icon = FaIcon(FontAwesomeIcons.paperPlane, size: 12, color: color); - text = context.lang.messageSendState_Opened; - case MessageSendState.received: - icon = Icon(Icons.square_rounded, size: 14, color: color); - text = context.lang.messageSendState_Received; - if (message.kind == MessageKind.media) { - if (message.downloadState == DownloadState.pending) { - text = context.lang.messageSendState_TapToLoad; - } - if (message.downloadState == DownloadState.downloading) { - text = context.lang.messageSendState_Loading; - icon = getLoaderIcon(color); - } - } - case MessageSendState.send: - icon = - FaIcon(FontAwesomeIcons.solidPaperPlane, size: 12, color: color); - text = context.lang.messageSendState_Send; - case MessageSendState.sending: - icon = getLoaderIcon(color); - text = context.lang.messageSendState_Sending; - case MessageSendState.receiving: - icon = getLoaderIcon(color); - text = context.lang.messageSendState_Received; - } - - if (message.kind == MessageKind.storedMediaFile) { - icon = FaIcon(FontAwesomeIcons.floppyDisk, size: 12, color: color); - text = context.lang.messageStoredInGallery; - } - - if (message.kind == MessageKind.reopenedMedia) { - icon = FaIcon(FontAwesomeIcons.repeat, size: 12, color: color); - text = context.lang.messageReopened; - } - - if (message.errorWhileSending) { - icon = - FaIcon(FontAwesomeIcons.circleExclamation, size: 12, color: color); - text = 'Error'; - } - - if (message.mediaRetransmissionState == MediaRetransmitting.requested) { - icon = FaIcon(FontAwesomeIcons.clockRotateLeft, size: 12, color: color); - textWidget = Text( - context.lang.retransmissionRequested, - style: const TextStyle(fontSize: 9), - ); - } - - if (message.kind == MessageKind.media) { - icons.insert(0, icon); - } else { - icons.add(icon); - } - } - if (icons.isEmpty) return Container(); var icon = icons[0]; @@ -215,7 +223,7 @@ class _MessageSendStateIconState extends State { icon, const SizedBox(width: 3), if (textWidget != null) - textWidget + textWidget! else Text( text, diff --git a/lib/src/views/chats/chat_messages_components/response_container.dart b/lib/src/views/chats/chat_messages_components/response_container.dart index c02d4b2..b46d6fd 100644 --- a/lib/src/views/chats/chat_messages_components/response_container.dart +++ b/lib/src/views/chats/chat_messages_components/response_container.dart @@ -1,29 +1,27 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/database/tables/messages_table.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; +import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; -import 'package:twonly/src/model/memory_item.model.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart'; class ResponseContainer extends StatefulWidget { const ResponseContainer({ required this.msg, - required this.contact, + required this.group, required this.child, required this.scrollToMessage, + required this.mediaService, super.key, }); - final ChatMessage msg; - final Widget child; - final Contact contact; - final void Function(int) scrollToMessage; + final Message msg; + final Widget? child; + final Group group; + final MediaFileService? mediaService; + final void Function(String) scrollToMessage; @override State createState() => _ResponseContainerState(); @@ -57,17 +55,20 @@ class _ResponseContainerState extends State { @override Widget build(BuildContext context) { - if (widget.msg.responseTo == null) { - return widget.child; + if (widget.msg.quotesMessageId == null) { + if (widget.child == null) { + return Container(); + } + return widget.child!; } return GestureDetector( - onTap: () => widget.scrollToMessage(widget.msg.responseTo!.messageId), + onTap: () => widget.scrollToMessage(widget.msg.quotesMessageId!), child: Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.8, ), decoration: BoxDecoration( - color: getMessageColor(widget.msg.message), + color: getMessageColor(widget.msg), borderRadius: BorderRadius.circular(12), ), child: Column( @@ -88,8 +89,8 @@ class _ResponseContainerState extends State { ), ), child: ResponsePreview( - contact: widget.contact, - message: widget.msg.responseTo!, + group: widget.group, + messageId: widget.msg.quotesMessageId, showBorder: false, ), ), @@ -108,14 +109,16 @@ class _ResponseContainerState extends State { class ResponsePreview extends StatefulWidget { const ResponsePreview({ - required this.message, - required this.contact, + required this.group, required this.showBorder, + this.message, + this.messageId, super.key, }); - final Message message; - final Contact contact; + final Message? message; + final String? messageId; + final Group group; final bool showBorder; @override @@ -123,56 +126,49 @@ class ResponsePreview extends StatefulWidget { } class _ResponsePreviewState extends State { - File? thumbnailPath; + Message? message; + MediaFileService? mediaService; @override void initState() { + message = widget.message; + initAsync(); super.initState(); - unawaited(initAsync()); } Future initAsync() async { - final items = await MemoryItem.convertFromMessages([widget.message]); - if (items.length == 1 && mounted) { - setState(() { - thumbnailPath = items.values.first.thumbnailPath; - }); + message ??= await twonlyDB.messagesDao + .getMessageById(widget.messageId!) + .getSingleOrNull(); + if (message?.mediaId != null) { + mediaService = await MediaFileService.fromMediaId(message!.mediaId!); } + if (mounted) setState(() {}); } @override Widget build(BuildContext context) { + if (message == null) return Container(); String? subtitle; - if (widget.message.kind == MessageKind.textMessage) { - if (widget.message.contentJson != null) { - final content = MessageContent.fromJson( - MessageKind.textMessage, - jsonDecode(widget.message.contentJson!) as Map, - ); - if (content is TextMessageContent) { - subtitle = truncateString(content.text); - } + if (message!.type == MessageType.text) { + if (message!.content != null) { + subtitle = truncateString(message!.content!); } } - if (widget.message.kind == MessageKind.media) { - final content = MessageContent.fromJson( - MessageKind.media, - jsonDecode(widget.message.contentJson!) as Map, - ); - if (content is MediaMessageContent) { - subtitle = content.isVideo ? 'Video' : 'Image'; - } + if (message!.type == MessageType.media && mediaService != null) { + subtitle = + mediaService!.mediaFile.type == MediaType.video ? 'Video' : 'Image'; } var username = 'You'; - if (widget.message.messageOtherId != null) { - username = getContactDisplayName(widget.contact); + if (message!.senderId != null) { + username = message!.senderId.toString(); } - final color = getMessageColor(widget.message); + final color = getMessageColor(message!); - if (!widget.message.mediaStored) { + if (!message!.mediaStored) { return Container( padding: widget.showBorder ? const EdgeInsets.only(left: 10, right: 10) @@ -225,10 +221,10 @@ class _ResponsePreviewState extends State { ], ), ), - if (thumbnailPath != null) + if (mediaService != null) SizedBox( height: widget.showBorder ? 100 : 210, - child: Image.file(thumbnailPath!), + child: Image.file(mediaService!.thumbnailPath), ), ], ), diff --git a/lib/src/views/chats/media_viewer.view.dart b/lib/src/views/chats/media_viewer.view.dart index 887c1b6..5a17af7 100644 --- a/lib/src/views/chats/media_viewer.view.dart +++ b/lib/src/views/chats/media_viewer.view.dart @@ -1,29 +1,24 @@ -// ignore_for_file: avoid_dynamic_calls - import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:lottie/lottie.dart'; import 'package:no_screenshot/no_screenshot.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/database/tables/messages_table.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart' + show DownloadState, MediaType; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/message_old.dart'; -import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' + as pb; import 'package:twonly/src/services/api/mediafiles/download.service.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/camera/camera_send_to_view.dart'; -import 'package:twonly/src/views/camera/share_image_editor_view.dart'; +import 'package:twonly/src/views/chats/media_viewer_components/reaction_buttons.component.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart'; import 'package:video_player/video_player.dart'; @@ -31,8 +26,8 @@ import 'package:video_player/video_player.dart'; final NoScreenshot _noScreenshot = NoScreenshot.instance; class MediaViewerView extends StatefulWidget { - const MediaViewerView(this.contact, {super.key, this.initialMessage}); - final Contact contact; + const MediaViewerView(this.group, {super.key, this.initialMessage}); + final Group group; final Message? initialMessage; @@ -48,23 +43,21 @@ class _MediaViewerViewState extends State { double mediaViewerDistanceFromBottom = 0; // current image related - Uint8List? imageBytes; - String? videoPath; VideoPlayerController? videoController; + MediaFileService? currentMedia; + Message? currentMessage; + DateTime? canBeSeenUntil; - int maxShowTime = gMediaShowInfinite; double progress = 0; - bool isRealTwonly = false; - bool mirrorVideo = false; - bool isDownloading = false; bool showSendTextMessageInput = false; final GlobalKey mediaWidgetKey = GlobalKey(); bool imageSaved = false; bool imageSaving = false; + bool displayTwonlyPresent = true; - StreamSubscription? downloadStateListener; + StreamSubscription? downloadStateListener; List allMediaFiles = []; late StreamSubscription> _subscription; @@ -94,20 +87,21 @@ class _MediaViewerViewState extends State { Future asyncLoadNextMedia(bool firstRun) async { final messages = - twonlyDB.messagesDao.watchMediaMessageNotOpened(widget.contact.userId); + twonlyDB.messagesDao.watchMediaNotOpened(widget.group.groupId); - _subscription = messages.listen((messages) { + _subscription = messages.listen((messages) async { for (final msg in messages) { - // if (!allMediaFiles.any((m) => m.messageId == msg.messageId)) { - // allMediaFiles.add(msg); - // } - // Find the index of the existing message with the same messageId + if (msg.mediaId == currentMedia?.mediaFile.mediaId) { + // The update of the current Media in case of a download is done in loadCurrentMediaFile + continue; + } + + /// If the messages was already there just replace it and go to the next... + final index = allMediaFiles.indexWhere((m) => m.messageId == msg.messageId); if (index >= 1) { - // to not modify the first message - // If the message exists, replace it allMediaFiles[index] = msg; } else if (index == -1) { // If the message does not exist, add it @@ -116,101 +110,90 @@ class _MediaViewerViewState extends State { } setState(() {}); if (firstRun) { - loadCurrentMediaFile(); // ignore: parameter_assignments firstRun = false; + await loadCurrentMediaFile(); } }); } Future nextMediaOrExit() async { - if (!mounted) return; - await videoController?.dispose(); - nextMediaTimer?.cancel(); - progressTimer?.cancel(); - if (allMediaFiles.isNotEmpty) { - try { - if (!imageSaved && maxShowTime != gMediaShowInfinite) { - await deleteMediaFile(allMediaFiles.first.messageId, 'mp4'); - await deleteMediaFile(allMediaFiles.first.messageId, 'png'); - } - } catch (e) { - Log.error('$e'); + /// Remove the current media file in case it is not set to unlimited + if (currentMedia != null) { + if (!imageSaved && + currentMedia!.mediaFile.displayLimitInMilliseconds != null) { + currentMedia!.fullMediaRemoval(); } } - if (allMediaFiles.isEmpty || allMediaFiles.length == 1) { - if (mounted) { - Navigator.pop(context); - } + + await videoController?.dispose(); + if (!mounted) return; + + nextMediaTimer?.cancel(); + progressTimer?.cancel(); + + if (allMediaFiles.isEmpty) { + Navigator.pop(context); } else { - allMediaFiles.removeAt(0); await loadCurrentMediaFile(); } } Future loadCurrentMediaFile({bool showTwonly = false}) async { - if (!mounted) return; - if (!context.mounted || allMediaFiles.isEmpty) return nextMediaOrExit(); + if (!mounted || !context.mounted) return; + if (allMediaFiles.isEmpty) return nextMediaOrExit(); await _noScreenshot.screenshotOff(); setState(() { videoController = null; - imageBytes = null; + currentMedia = null; + currentMessage = null; canBeSeenUntil = null; - maxShowTime = gMediaShowInfinite; imageSaving = false; imageSaved = false; - mirrorVideo = false; progress = 0; - videoPath = null; - isDownloading = false; - isRealTwonly = false; showSendTextMessageInput = false; }); - if (Platform.isAndroid) { - await flutterLocalNotificationsPlugin - .cancel(allMediaFiles.first.contactId); - } else { - await flutterLocalNotificationsPlugin.cancelAll(); - } + // if (Platform.isAndroid) { + // await flutterLocalNotificationsPlugin + // .cancel(allMediaFiles.first.contactId); + // } else { + await flutterLocalNotificationsPlugin.cancelAll(); + // } - if (allMediaFiles.first.downloadState != DownloadState.downloaded) { - setState(() { - isDownloading = true; - }); - await startDownloadMedia(allMediaFiles.first, true); + final stream = + twonlyDB.mediaFilesDao.watchMedia(currentMedia!.mediaFile.mediaId); - final stream = twonlyDB.messagesDao - .getMessageByMessageId(allMediaFiles.first.messageId) - .watchSingleOrNull(); - await downloadStateListener?.cancel(); - downloadStateListener = stream.listen((updated) async { - if (updated != null) { - if (updated.downloadState == DownloadState.downloaded) { - await downloadStateListener?.cancel(); - await handleNextDownloadedMedia(updated, showTwonly); - // start downloading all the other possible missing media files. - await tryDownloadAllMediaFiles(force: true); - } + var downloadTriggered = false; + + await downloadStateListener?.cancel(); + downloadStateListener = stream.listen((updated) async { + if (updated == null) return; + if (updated.downloadState != DownloadState.downloaded) { + if (!downloadTriggered) { + downloadTriggered = true; + await startDownloadMedia(currentMedia!.mediaFile, true); + unawaited(tryDownloadAllMediaFiles(force: true)); } - }); - } else { - await handleNextDownloadedMedia(allMediaFiles.first, showTwonly); - } + return; + } + + await downloadStateListener?.cancel(); + await handleNextDownloadedMedia(showTwonly); + // start downloading all the other possible missing media files. + }); } Future handleNextDownloadedMedia( - Message current, bool showTwonly, ) async { - final content = - MediaMessageContent.fromJson(jsonDecode(current.contentJson!) as Map); + currentMessage = allMediaFiles.removeAt(0); + final currentMediaLocal = + await MediaFileService.fromMediaId(currentMessage!.mediaId!); + if (currentMediaLocal == null || !mounted) return; - if (content.isRealTwonly) { - setState(() { - isRealTwonly = true; - }); + if (currentMediaLocal.mediaFile.requiresAuthentication) { if (!showTwonly) return; final isAuth = await authenticateUser( @@ -224,66 +207,56 @@ class _MediaViewerViewState extends State { } await notifyContactAboutOpeningMessage( - current.contactId, - [current.messageOtherId!], + currentMessage!.senderId!, + [currentMessage!.messageId], ); - await twonlyDB.messagesDao.updateMessageByMessageId( - current.messageId, + await twonlyDB.messagesDao.updateMessageId( + currentMessage!.messageId, MessagesCompanion(openedAt: Value(DateTime.now())), ); - if (content.isVideo) { - final videoPathTmp = await getVideoPath(current.messageId); - if (videoPathTmp != null) { - videoController = VideoPlayerController.file(File(videoPathTmp.path)); - await videoController - ?.setLooping(content.maxShowTime == gMediaShowInfinite); - await videoController?.initialize().then((_) { - videoController!.play(); - videoController?.addListener(() { - setState(() { - progress = 1 - - videoController!.value.position.inSeconds / - videoController!.value.duration.inSeconds; - }); - if (content.maxShowTime != gMediaShowInfinite) { - if (videoController?.value.position == - videoController?.value.duration) { - nextMediaOrExit(); - } - } - }); - setState(() { - videoPath = videoPathTmp.path; - }); - // ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler - }).catchError(Log.error); - } - } - - imageBytes = await getImageBytes(current.messageId); - - if ((imageBytes == null && !content.isVideo) || - (content.isVideo && videoController == null)) { - Log.error('media files are not found...'); - // When the message should be downloaded but imageBytes are null then a error happened - await handleMediaError(current); + if (!currentMediaLocal.tempPath.existsSync()) { + Log.error('Temp media file not found...'); + await handleMediaError(currentMediaLocal.mediaFile); return nextMediaOrExit(); } - if (!content.isVideo) { - if (content.maxShowTime != gMediaShowInfinite) { + if (currentMediaLocal.mediaFile.type == MediaType.video) { + videoController = VideoPlayerController.file(currentMediaLocal.tempPath); + await videoController?.setLooping( + currentMediaLocal.mediaFile.displayLimitInMilliseconds == null, + ); + await videoController?.initialize().then((_) { + videoController!.play(); + videoController?.addListener(() { + setState(() { + progress = 1 - + videoController!.value.position.inSeconds / + videoController!.value.duration.inSeconds; + }); + if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) { + if (videoController?.value.position == + videoController?.value.duration) { + nextMediaOrExit(); + } + } + }); + // ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler + }).catchError(Log.error); + } else { + if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) { canBeSeenUntil = DateTime.now().add( - Duration(seconds: content.maxShowTime), + Duration( + milliseconds: + currentMediaLocal.mediaFile.displayLimitInMilliseconds!, + ), ); startTimer(); } } setState(() { - maxShowTime = content.maxShowTime; - isDownloading = false; - mirrorVideo = content.mirrorVideo; + currentMedia = currentMediaLocal; }); } @@ -299,44 +272,37 @@ class _MediaViewerViewState extends State { if (canBeSeenUntil != null) { final difference = canBeSeenUntil!.difference(DateTime.now()); // Calculate the progress as a value between 0.0 and 1.0 - progress = difference.inMilliseconds / (maxShowTime * 1000); + progress = difference.inMilliseconds / + (currentMedia!.mediaFile.displayLimitInMilliseconds!); setState(() {}); } }); } Future onPressedSaveToGallery() async { - if (allMediaFiles.first.messageOtherId == null) { - return; // should not be possible - } setState(() { imageSaving = true; }); - await twonlyDB.messagesDao.updateMessageByMessageId( - allMediaFiles.first.messageId, - const MessagesCompanion(mediaStored: Value(true)), - ); - await encryptAndSendMessageAsync( - null, - widget.contact.userId, - MessageJson( - kind: MessageKind.storedMediaFile, - messageSenderId: allMediaFiles.first.messageId, - messageReceiverId: allMediaFiles.first.messageOtherId, - content: MessageContent(), - timestamp: DateTime.now(), + await currentMedia!.storeMediaFile(); + await sendCipherTextToGroup( + widget.group.groupId, + pb.EncryptedContent( + mediaUpdate: pb.EncryptedContent_MediaUpdate( + type: pb.EncryptedContent_MediaUpdate_Type.STORED, + targetMediaId: currentMedia!.mediaFile.mediaId, + ), ), - pushNotification: PushNotification(kind: PushKind.storedMediaFile), ); setState(() { imageSaved = true; }); - final user = await getUser(); - if (user != null && (user.storeMediaFilesInGallery)) { - if (videoPath != null) { - await saveVideoToGallery(videoPath!); - } else { - await saveImageToGallery(imageBytes!); + + if (gUser.storeMediaFilesInGallery) { + if (currentMedia!.mediaFile.type == MediaType.video) { + await saveVideoToGallery(currentMedia!.storedPath.path); + } else if (currentMedia!.mediaFile.type == MediaType.image) { + final imageBytes = await currentMedia!.storedPath.readAsBytes(); + await saveImageToGallery(imageBytes); } } setState(() { @@ -358,7 +324,8 @@ class _MediaViewerViewState extends State { key: mediaWidgetKey, mainAxisAlignment: MainAxisAlignment.center, children: [ - if (maxShowTime == gMediaShowInfinite) + if (currentMedia != null && + currentMedia!.mediaFile.displayLimitInMilliseconds == null) OutlinedButton( style: OutlinedButton.styleFrom( iconColor: imageSaved @@ -368,7 +335,7 @@ class _MediaViewerViewState extends State { ? Theme.of(context).colorScheme.outline : Theme.of(context).colorScheme.primary, ), - onPressed: onPressedSaveToGallery, + onPressed: (currentMedia == null) ? null : onPressedSaveToGallery, child: Row( children: [ if (imageSaving) @@ -450,11 +417,12 @@ class _MediaViewerViewState extends State { context, MaterialPageRoute( builder: (context) { - return CameraSendToView(widget.contact); + return CameraSendToView(widget.group); }, ), ); - if (mounted && maxShowTime != gMediaShowInfinite) { + if (mounted && + currentMedia!.mediaFile.displayLimitInMilliseconds != null) { await nextMediaOrExit(); } else { await videoController?.play(); @@ -477,7 +445,7 @@ class _MediaViewerViewState extends State { child: Stack( fit: StackFit.expand, children: [ - if ((imageBytes != null || videoController != null) && + if ((currentMedia != null || videoController != null) && (canBeSeenUntil == null || progress >= 0)) GestureDetector( onTap: () { @@ -497,15 +465,12 @@ class _MediaViewerViewState extends State { children: [ if (videoController != null) Positioned.fill( - child: Transform.flip( - flipX: mirrorVideo, - child: VideoPlayer(videoController!), - ), + child: VideoPlayer(videoController!), ), - if (imageBytes != null) + if (currentMedia!.mediaFile.type == MediaType.image) Positioned.fill( - child: Image.memory( - imageBytes!, + child: Image.file( + currentMedia!.tempPath, fit: BoxFit.contain, frameBuilder: ( context, @@ -534,7 +499,9 @@ class _MediaViewerViewState extends State { ), ), ), - if (isRealTwonly && imageBytes == null) + if (currentMedia != null && + currentMedia!.mediaFile.requiresAuthentication && + displayTwonlyPresent) Positioned.fill( child: GestureDetector( onTap: () { @@ -570,7 +537,8 @@ class _MediaViewerViewState extends State { ], ), ), - if (isDownloading) + if (currentMedia?.mediaFile.downloadState != + DownloadState.downloaded) const Positioned.fill( child: Center( child: SizedBox( @@ -602,7 +570,7 @@ class _MediaViewerViewState extends State { left: showSendTextMessageInput ? 0 : null, right: showSendTextMessageInput ? 0 : 15, child: Text( - getContactDisplayName(widget.contact), + widget.group.groupName, textAlign: TextAlign.center, style: TextStyle( fontSize: showSendTextMessageInput ? 24 : 14, @@ -658,18 +626,12 @@ class _MediaViewerViewState extends State { ), IconButton( icon: const FaIcon(FontAwesomeIcons.solidPaperPlane), - onPressed: () { + onPressed: () async { if (textMessageController.text.isNotEmpty) { - sendTextMessage( - widget.contact.userId, - TextMessageContent( - text: textMessageController.text, - responseToMessageId: - allMediaFiles.first.messageOtherId, - ), - PushNotification( - kind: PushKind.response, - ), + await insertAndSendTextMessage( + widget.group.groupId, + textMessageController.text, + currentMessage!.messageId, ); textMessageController.clear(); } @@ -683,14 +645,13 @@ class _MediaViewerViewState extends State { ), ), ), - if (allMediaFiles.isNotEmpty) + if (currentMedia != null) ReactionButtons( show: showShortReactions, textInputFocused: showSendTextMessageInput, mediaViewerDistanceFromBottom: mediaViewerDistanceFromBottom, - userId: widget.contact.userId, - responseToMessageId: allMediaFiles.first.messageOtherId!, - isVideo: videoController != null, + groupId: widget.group.groupId, + messageId: currentMessage!.messageId, hide: () { setState(() { showShortReactions = false; @@ -704,195 +665,3 @@ class _MediaViewerViewState extends State { ); } } - -class ReactionButtons extends StatefulWidget { - const ReactionButtons({ - required this.show, - required this.textInputFocused, - required this.userId, - required this.mediaViewerDistanceFromBottom, - required this.responseToMessageId, - required this.isVideo, - required this.hide, - super.key, - }); - - final double mediaViewerDistanceFromBottom; - final bool show; - final bool isVideo; - final bool textInputFocused; - final int userId; - final int responseToMessageId; - final void Function() hide; - - @override - State createState() => _ReactionButtonsState(); -} - -class _ReactionButtonsState extends State { - int selectedShortReaction = -1; - - List selectedEmojis = - EmojiAnimation.animatedIcons.keys.toList().sublist(0, 6); - - @override - void initState() { - super.initState(); - initAsync(); - } - - Future initAsync() async { - final user = await getUser(); - if (user != null && user.preSelectedEmojies != null) { - selectedEmojis = user.preSelectedEmojies!; - } - setState(() {}); - } - - @override - Widget build(BuildContext context) { - final firstRowEmojis = selectedEmojis.take(6).toList(); - final secondRowEmojis = - selectedEmojis.length > 6 ? selectedEmojis.skip(6).toList() : []; - - return AnimatedPositioned( - duration: const Duration(milliseconds: 200), // Animation duration - bottom: widget.show - ? (widget.textInputFocused - ? 50 - : widget.mediaViewerDistanceFromBottom) - : widget.mediaViewerDistanceFromBottom - 20, - left: widget.show ? 0 : MediaQuery.sizeOf(context).width / 2, - right: widget.show ? 0 : MediaQuery.sizeOf(context).width / 2, - curve: Curves.linearToEaseOut, - child: AnimatedOpacity( - opacity: widget.show ? 1.0 : 0.0, // Fade in/out - duration: const Duration(milliseconds: 150), - child: Container( - color: widget.show ? Colors.black.withAlpha(0) : Colors.transparent, - padding: - widget.show ? const EdgeInsets.symmetric(vertical: 32) : null, - child: Column( - children: [ - if (secondRowEmojis.isNotEmpty) - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.end, - children: secondRowEmojis - .map( - (emoji) => EmojiReactionWidget( - userId: widget.userId, - responseToMessageId: widget.responseToMessageId, - hide: widget.hide, - show: widget.show, - isVideo: widget.isVideo, - emoji: emoji as String, - ), - ) - .toList(), - ), - if (secondRowEmojis.isNotEmpty) const SizedBox(height: 15), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.end, - children: firstRowEmojis - .map( - (emoji) => EmojiReactionWidget( - userId: widget.userId, - responseToMessageId: widget.responseToMessageId, - hide: widget.hide, - show: widget.show, - isVideo: widget.isVideo, - emoji: emoji, - ), - ) - .toList(), - ), - ], - ), - ), - ), - ); - } -} - -class EmojiReactionWidget extends StatefulWidget { - const EmojiReactionWidget({ - required this.userId, - required this.responseToMessageId, - required this.hide, - required this.isVideo, - required this.show, - required this.emoji, - super.key, - }); - final int userId; - final int responseToMessageId; - final Function hide; - final bool show; - final bool isVideo; - final String emoji; - - @override - State createState() => _EmojiReactionWidgetState(); -} - -class _EmojiReactionWidgetState extends State { - int selectedShortReaction = -1; - - @override - Widget build(BuildContext context) { - return AnimatedSize( - duration: const Duration(milliseconds: 200), - curve: Curves.linearToEaseOut, - child: GestureDetector( - onTap: () { - sendTextMessage( - widget.userId, - TextMessageContent( - text: widget.emoji, - responseToMessageId: widget.responseToMessageId, - ), - PushNotification( - kind: widget.isVideo - ? PushKind.reactionToVideo - : PushKind.reactionToImage, - reactionContent: widget.emoji, - ), - ); - setState(() { - selectedShortReaction = 0; // Assuming index is 0 for this example - }); - Future.delayed(const Duration(milliseconds: 300), () { - if (mounted) { - setState(() { - widget.hide(); - selectedShortReaction = -1; - }); - } - }); - }, - child: (selectedShortReaction == - 0) // Assuming index is 0 for this example - ? EmojiAnimationFlying( - emoji: widget.emoji, - duration: const Duration(milliseconds: 300), - startPosition: 0, - size: (widget.show) ? 40 : 10, - ) - : AnimatedOpacity( - opacity: (selectedShortReaction == -1) ? 1 : 0, // Fade in/out - duration: const Duration(milliseconds: 150), - child: SizedBox( - width: widget.show ? 40 : 10, - child: Center( - child: EmojiAnimation( - emoji: widget.emoji, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart b/lib/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart new file mode 100644 index 0000000..bea166e --- /dev/null +++ b/lib/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart @@ -0,0 +1,92 @@ +// ignore_for_file: avoid_dynamic_calls + +import 'package:drift/drift.dart' show Value; +import 'package:flutter/material.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; +import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/views/components/animate_icon.dart'; + +class EmojiReactionWidget extends StatefulWidget { + const EmojiReactionWidget({ + required this.messageId, + required this.groupId, + required this.hide, + required this.show, + required this.emoji, + super.key, + }); + final String messageId; + final String groupId; + final Function hide; + final bool show; + final String emoji; + + @override + State createState() => _EmojiReactionWidgetState(); +} + +class _EmojiReactionWidgetState extends State { + int selectedShortReaction = -1; + + @override + Widget build(BuildContext context) { + return AnimatedSize( + duration: const Duration(milliseconds: 200), + curve: Curves.linearToEaseOut, + child: GestureDetector( + onTap: () async { + await twonlyDB.reactionsDao.insertReaction( + ReactionsCompanion( + messageId: Value(widget.messageId), + emoji: Value(widget.emoji), + ), + ); + + await sendCipherTextToGroup( + widget.groupId, + EncryptedContent( + reaction: EncryptedContent_Reaction( + targetMessageId: widget.messageId, + emoji: widget.emoji, + ), + ), + ); + + setState(() { + selectedShortReaction = 0; // Assuming index is 0 for this example + }); + Future.delayed(const Duration(milliseconds: 300), () { + if (mounted) { + setState(() { + widget.hide(); + selectedShortReaction = -1; + }); + } + }); + }, + child: (selectedShortReaction == + 0) // Assuming index is 0 for this example + ? EmojiAnimationFlying( + emoji: widget.emoji, + duration: const Duration(milliseconds: 300), + startPosition: 0, + size: (widget.show) ? 40 : 10, + ) + : AnimatedOpacity( + opacity: (selectedShortReaction == -1) ? 1 : 0, // Fade in/out + duration: const Duration(milliseconds: 150), + child: SizedBox( + width: widget.show ? 40 : 10, + child: Center( + child: EmojiAnimation( + emoji: widget.emoji, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart new file mode 100644 index 0000000..75146c4 --- /dev/null +++ b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart @@ -0,0 +1,112 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart'; +import 'package:twonly/src/views/components/animate_icon.dart'; + +class ReactionButtons extends StatefulWidget { + const ReactionButtons({ + required this.show, + required this.textInputFocused, + required this.mediaViewerDistanceFromBottom, + required this.messageId, + required this.groupId, + required this.hide, + super.key, + }); + + final double mediaViewerDistanceFromBottom; + final bool show; + final bool textInputFocused; + final String messageId; + final String groupId; + final void Function() hide; + + @override + State createState() => _ReactionButtonsState(); +} + +class _ReactionButtonsState extends State { + int selectedShortReaction = -1; + + List selectedEmojis = + EmojiAnimation.animatedIcons.keys.toList().sublist(0, 6); + + @override + void initState() { + super.initState(); + initAsync(); + } + + Future initAsync() async { + final user = await getUser(); + if (user != null && user.preSelectedEmojies != null) { + selectedEmojis = user.preSelectedEmojies!; + } + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final firstRowEmojis = selectedEmojis.take(6).toList(); + final secondRowEmojis = + selectedEmojis.length > 6 ? selectedEmojis.skip(6).toList() : []; + + return AnimatedPositioned( + duration: const Duration(milliseconds: 200), // Animation duration + bottom: widget.show + ? (widget.textInputFocused + ? 50 + : widget.mediaViewerDistanceFromBottom) + : widget.mediaViewerDistanceFromBottom - 20, + left: widget.show ? 0 : MediaQuery.sizeOf(context).width / 2, + right: widget.show ? 0 : MediaQuery.sizeOf(context).width / 2, + curve: Curves.linearToEaseOut, + child: AnimatedOpacity( + opacity: widget.show ? 1.0 : 0.0, // Fade in/out + duration: const Duration(milliseconds: 150), + child: Container( + color: widget.show ? Colors.black.withAlpha(0) : Colors.transparent, + padding: + widget.show ? const EdgeInsets.symmetric(vertical: 32) : null, + child: Column( + children: [ + if (secondRowEmojis.isNotEmpty) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.end, + children: secondRowEmojis + .map( + (emoji) => EmojiReactionWidget( + messageId: widget.messageId, + groupId: widget.groupId, + hide: widget.hide, + show: widget.show, + emoji: emoji as String, + ), + ) + .toList(), + ), + if (secondRowEmojis.isNotEmpty) const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.end, + children: firstRowEmojis + .map( + (emoji) => EmojiReactionWidget( + messageId: widget.messageId, + groupId: widget.groupId, + hide: widget.hide, + show: widget.show, + emoji: emoji, + ), + ) + .toList(), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/views/chats/start_new_chat.view.dart b/lib/src/views/chats/start_new_chat.view.dart index d4dbcac..2a0829c 100644 --- a/lib/src/views/chats/start_new_chat.view.dart +++ b/lib/src/views/chats/start_new_chat.view.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -10,8 +9,8 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/flame.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/user_context_menu.component.dart'; class StartNewChatView extends StatefulWidget { @@ -30,7 +29,7 @@ class _StartNewChatView extends State { void initState() { super.initState(); - final stream = twonlyDB.contactsDao.watchContactsForStartNewChat(); + final stream = twonlyDB.contactsDao.watchAllAcceptedContacts(); contactSub = stream.listen((update) async { update.sort( @@ -147,7 +146,6 @@ class UserList extends StatelessWidget { return const Divider(); } final user = users[i - 2]; - final flameCounter = getFlameCounterFromContact(user); return UserContextMenu( key: Key(user.userId.toString()), contact: user, @@ -155,40 +153,37 @@ class UserList extends StatelessWidget { title: Row( children: [ Text(getContactDisplayName(user)), - if (flameCounter >= 1) - FlameCounterWidget( - user, - flameCounter, - prefix: true, - ), - const Spacer(), - IconButton( - icon: FaIcon( - FontAwesomeIcons.boxOpen, - size: 13, - color: user.archived ? null : Colors.transparent, - ), - onPressed: user.archived - ? () async { - const update = - ContactsCompanion(archived: Value(false)); - await twonlyDB.contactsDao - .updateContact(user.userId, update); - } - : null, + FlameCounterWidget( + contactId: user.userId, + prefix: true, ), ], ), - leading: ContactAvatar( + leading: AvatarIcon( contact: user, fontSize: 13, ), onTap: () async { + var directChat = + await twonlyDB.groupsDao.getDirectChat(user.userId); + if (directChat == null) { + await twonlyDB.groupsDao.insertGroup( + GroupsCompanion( + isDirectChat: const Value(true), + groupName: Value( + getContactDisplayName(user), + ), + ), + ); + directChat = + await twonlyDB.groupsDao.getDirectChat(user.userId); + } + if (!context.mounted) return; await Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) { - return ChatMessagesView(user); + return ChatMessagesView(directChat!); }, ), ); diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/views/components/avatar_icon.component.dart new file mode 100644 index 0000000..8e9706e --- /dev/null +++ b/lib/src/views/components/avatar_icon.component.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/json/userdata.dart'; +import 'package:twonly/src/utils/log.dart'; + +class AvatarIcon extends StatefulWidget { + const AvatarIcon({ + super.key, + this.group, + this.contact, + this.userData, + this.fontSize = 20, + this.color, + }); + final Group? group; + final Contact? contact; + final UserData? userData; + final double? fontSize; + final Color? color; + + @override + State createState() => _AvatarIconState(); +} + +class _AvatarIconState extends State { + List avatarSVGs = []; + + @override + void initState() { + initAsync(); + super.initState(); + } + + Future initAsync() async { + if (widget.group != null) { + final contacts = + await twonlyDB.groupsDao.getGroupContact(widget.group!.groupId); + if (contacts.length == 1) { + if (contacts.first.avatarSvg != null) { + avatarSVGs.add(contacts.first.avatarSvg!); + } + } else { + for (final contact in contacts) { + if (contact.avatarSvg != null) { + avatarSVGs.add(contact.avatarSvg!); + } + } + } + // avatarSvg = group!.avatarSvg; + } else if (widget.userData?.avatarSvg != null) { + avatarSVGs.add(widget.userData!.avatarSvg!); + } else if (widget.contact?.avatarSvg != null) { + avatarSVGs.add(widget.contact!.avatarSvg!); + } + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + final proSize = (widget.fontSize == null) ? 40 : (widget.fontSize! * 2); + + return Container( + constraints: BoxConstraints( + minHeight: 2 * (widget.fontSize ?? 20), + minWidth: 2 * (widget.fontSize ?? 20), + maxWidth: 2 * (widget.fontSize ?? 20), + maxHeight: 2 * (widget.fontSize ?? 20), + ), + child: Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Container( + height: proSize as double, + width: proSize, + color: widget.color, + child: Center( + child: avatarSVGs.isEmpty + ? SvgPicture.asset('assets/images/default_avatar.svg') + : SvgPicture.string( + avatarSVGs.first, + errorBuilder: (context, error, stackTrace) { + Log.error('$error'); + return Container(); + }, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/views/components/flame.dart b/lib/src/views/components/flame.dart index b37c01c..7753e55 100644 --- a/lib/src/views/components/flame.dart +++ b/lib/src/views/components/flame.dart @@ -1,26 +1,65 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; -class FlameCounterWidget extends StatelessWidget { - const FlameCounterWidget( - this.user, - this.flameCounter, { +class FlameCounterWidget extends StatefulWidget { + const FlameCounterWidget({ + this.groupId, + this.contactId, this.prefix = false, super.key, }); - final Contact user; - final int flameCounter; + final String? groupId; + final int? contactId; final bool prefix; + @override + State createState() => _FlameCounterWidgetState(); +} + +class _FlameCounterWidgetState extends State { + int flameCounter = 0; + bool isBestFriend = false; + + StreamSubscription? flameCounterSub; + + @override + void initState() { + initAsync(); + super.initState(); + } + + @override + void dispose() { + flameCounterSub?.cancel(); + super.dispose(); + } + + Future initAsync() async { + var groupId = widget.groupId; + if (widget.groupId == null && widget.contactId != null) { + final group = await twonlyDB.groupsDao.getDirectChat(widget.contactId!); + groupId = group?.groupId; + } + if (groupId != null) { + isBestFriend = gUser.myBestFriendGroupId == groupId; + final stream = twonlyDB.groupsDao.watchFlameCounter(groupId); + flameCounterSub = stream.listen((counter) { + setState(() { + flameCounter = counter; + }); + }); + } + } + @override Widget build(BuildContext context) { return Row( children: [ - if (prefix) const SizedBox(width: 5), - if (prefix) const Text('•'), - if (prefix) const SizedBox(width: 5), + if (widget.prefix) const SizedBox(width: 5), + if (widget.prefix) const Text('•'), + if (widget.prefix) const SizedBox(width: 5), Text( flameCounter.toString(), style: const TextStyle(fontSize: 13), @@ -28,7 +67,7 @@ class FlameCounterWidget extends StatelessWidget { SizedBox( height: 15, child: EmojiAnimation( - emoji: (globalBestFriendUserId == user.userId) ? '❤️‍🔥' : '🔥', + emoji: isBestFriend ? '❤️‍🔥' : '🔥', ), ), ], diff --git a/lib/src/views/components/initialsavatar.dart b/lib/src/views/components/initialsavatar.dart deleted file mode 100644 index ec7ae11..0000000 --- a/lib/src/views/components/initialsavatar.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/userdata.dart'; -import 'package:twonly/src/utils/log.dart'; - -class ContactAvatar extends StatelessWidget { - const ContactAvatar({ - super.key, - this.contact, - this.userData, - this.fontSize = 20, - this.color, - }); - final Contact? contact; - final UserData? userData; - final double? fontSize; - final Color? color; - - @override - Widget build(BuildContext context) { - String? avatarSvg; - - if (contact != null) { - avatarSvg = contact!.avatarSvg; - } else if (userData != null) { - avatarSvg = userData!.avatarSvg; - } else { - return Container(); - } - - final proSize = (fontSize == null) ? 40 : (fontSize! * 2); - - return Container( - constraints: BoxConstraints( - minHeight: 2 * (fontSize ?? 20), - minWidth: 2 * (fontSize ?? 20), - maxWidth: 2 * (fontSize ?? 20), - maxHeight: 2 * (fontSize ?? 20), - ), - child: Center( - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Container( - height: proSize as double, - width: proSize, - color: color, - child: Center( - child: avatarSvg == null - ? SvgPicture.asset('assets/images/default_avatar.svg') - : SvgPicture.string( - avatarSvg, - errorBuilder: (context, error, stackTrace) { - Log.error('$error'); - return Container(); - }, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/src/views/components/user_context_menu.component.dart b/lib/src/views/components/user_context_menu.component.dart index 46c8716..2d0f004 100644 --- a/lib/src/views/components/user_context_menu.component.dart +++ b/lib/src/views/components/user_context_menu.component.dart @@ -5,8 +5,8 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/contact/contact.view.dart'; -class UserContextMenuBlocked extends StatefulWidget { - const UserContextMenuBlocked({ +class UserContextMenu extends StatefulWidget { + const UserContextMenu({ required this.contact, required this.child, super.key, @@ -15,10 +15,10 @@ class UserContextMenuBlocked extends StatefulWidget { final Contact contact; @override - State createState() => _UserContextMenuBlocked(); + State createState() => _UserContextMenuBlocked(); } -class _UserContextMenuBlocked extends State { +class _UserContextMenuBlocked extends State { @override Widget build(BuildContext context) { return PieMenu( diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index 20d6a46..7b51f36 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -7,9 +7,8 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; -import 'package:twonly/src/views/components/flame.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/verified_shield.dart'; import 'package:twonly/src/views/contact/contact_verify.view.dart'; @@ -105,12 +104,12 @@ class _ContactViewState extends State { return Container(); } final contact = snapshot.data!; - final flameCounter = getFlameCounterFromContact(contact); + // final flameCounter = getFlameCounterFromContact(contact); return ListView( children: [ Padding( padding: const EdgeInsets.all(10), - child: ContactAvatar(contact: contact, fontSize: 30), + child: AvatarIcon(contact: contact, fontSize: 30), ), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -123,12 +122,12 @@ class _ContactViewState extends State { getContactDisplayName(contact), style: const TextStyle(fontSize: 20), ), - if (flameCounter > 0) - FlameCounterWidget( - contact, - flameCounter, - prefix: true, - ), + // if (flameCounter > 0) + // FlameCounterWidget( + // contact, + // flameCounter, + // prefix: true, + // ), ], ), if (getContactDisplayName(contact) != contact.username) diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart new file mode 100644 index 0000000..85aa283 --- /dev/null +++ b/lib/src/views/groups/group.view.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/src/database/twonly.db.dart'; + +class GroupView extends StatefulWidget { + const GroupView(this.group, {super.key}); + + final Group group; + + @override + State createState() => _GroupViewState(); +} + +class _GroupViewState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/src/views/memories/memories_photo_slider.view.dart b/lib/src/views/memories/memories_photo_slider.view.dart index 5d9f1b7..55e298b 100644 --- a/lib/src/views/memories/memories_photo_slider.view.dart +++ b/lib/src/views/memories/memories_photo_slider.view.dart @@ -208,7 +208,8 @@ class _MemoriesPhotoSliderViewState extends State { minScale: PhotoViewComputedScale.contained, maxScale: PhotoViewComputedScale.covered * 4.1, heroAttributes: PhotoViewHeroAttributes( - tag: item.mediaService.mediaFile.mediaId), + tag: item.mediaService.mediaFile.mediaId, + ), ) : PhotoViewGalleryPageOptions( imageProvider: FileImage(item.mediaService.storedPath), @@ -216,7 +217,8 @@ class _MemoriesPhotoSliderViewState extends State { minScale: PhotoViewComputedScale.contained, maxScale: PhotoViewComputedScale.covered * 4.1, heroAttributes: PhotoViewHeroAttributes( - tag: item.mediaService.mediaFile.mediaId), + tag: item.mediaService.mediaFile.mediaId, + ), ); } } diff --git a/lib/src/views/settings/developer/automated_testing.view.dart b/lib/src/views/settings/developer/automated_testing.view.dart index d2b55ee..52e17d8 100644 --- a/lib/src/views/settings/developer/automated_testing.view.dart +++ b/lib/src/views/settings/developer/automated_testing.view.dart @@ -43,20 +43,18 @@ class _AutomatedTestingViewState extends State { await twonlyDB.contactsDao.getContactsByUsername(username); for (final contact in contacts) { - final groups = + final group = await twonlyDB.groupsDao.getDirectChat(contact.userId); - - for (final group in groups) { - for (var i = 0; i < 200; i++) { - setState(() { - lotsOfMessagesStatus = - 'At message $i to ${contact.username}.'; - }); - await insertAndSendTextMessage( - group.groupId, - 'Message $i.', - ); - } + for (var i = 0; i < 200; i++) { + setState(() { + lotsOfMessagesStatus = + 'At message $i to ${contact.username}.'; + }); + await insertAndSendTextMessage( + group!.groupId, + 'Message $i.', + null, + ); } } }, diff --git a/lib/src/views/settings/developer/developer.view.dart b/lib/src/views/settings/developer/developer.view.dart index d4df29f..efa8812 100644 --- a/lib/src/views/settings/developer/developer.view.dart +++ b/lib/src/views/settings/developer/developer.view.dart @@ -1,9 +1,6 @@ import 'dart:async'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/settings/developer/automated_testing.view.dart'; import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart'; @@ -69,14 +66,14 @@ class _DeveloperSettingsViewState extends State { ); }, ), - if (kDebugMode) - ListTile( - title: const Text('FlameSync Test'), - onTap: () async { - await twonlyDB.contactsDao.modifyFlameCounterForTesting(); - await syncFlameCounters(); - }, - ), + // if (kDebugMode) + // ListTile( + // title: const Text('FlameSync Test'), + // onTap: () async { + // await twonlyDB.contactsDao.modifyFlameCounterForTesting(); + // await syncFlameCounters(); + // }, + // ), if (kDebugMode) ListTile( title: const Text('Automated Testing'), diff --git a/lib/src/views/settings/privacy_view_block.users.dart b/lib/src/views/settings/privacy_view_block.users.dart index 01c2b17..8b383f2 100644 --- a/lib/src/views/settings/privacy_view_block.users.dart +++ b/lib/src/views/settings/privacy_view_block.users.dart @@ -5,7 +5,7 @@ import 'package:twonly/globals.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/user_context_menu.component.dart'; class PrivacyViewBlockUsers extends StatefulWidget { @@ -108,7 +108,7 @@ class UserList extends StatelessWidget { itemCount: users.length, itemBuilder: (BuildContext context, int i) { final user = users[i]; - return UserContextMenuBlocked( + return UserContextMenu( contact: user, child: ListTile( title: Row( @@ -116,7 +116,7 @@ class UserList extends StatelessWidget { Text(getContactDisplayName(user)), ], ), - leading: ContactAvatar(contact: user, fontSize: 15), + leading: AvatarIcon(contact: user, fontSize: 15), trailing: Checkbox( value: user.blocked, onChanged: (bool? value) async { diff --git a/lib/src/views/settings/settings_main.view.dart b/lib/src/views/settings/settings_main.view.dart index b1ffe1c..c27b5a4 100644 --- a/lib/src/views/settings/settings_main.view.dart +++ b/lib/src/views/settings/settings_main.view.dart @@ -5,8 +5,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; -import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/settings/account.view.dart'; import 'package:twonly/src/views/settings/appearance.view.dart'; import 'package:twonly/src/views/settings/backup/backup.view.dart'; @@ -72,7 +72,7 @@ class _SettingsMainViewState extends State { color: context.color.surface.withAlpha(0), child: Row( children: [ - ContactAvatar( + AvatarIcon( userData: userData, fontSize: 30, ),