From 48de7faaa24e0ff9dc7aa1b56bc6da22e4753593 Mon Sep 17 00:00:00 2001 From: otsmr Date: Mon, 3 Nov 2025 11:22:22 +0100 Subject: [PATCH] fixing avatar icon --- .../NotificationService.swift | 22 +++--- lib/globals.dart | 4 + lib/main.dart | 2 + lib/src/database/daos/groups.dao.dart | 4 + lib/src/database/daos/messages.dao.dart | 10 +++ lib/src/database/tables/groups.table.dart | 1 + lib/src/database/twonly.db.g.dart | 60 +++++++++++++- lib/src/localization/app_de.arb | 11 +-- lib/src/localization/app_en.arb | 11 +-- .../generated/app_localizations.dart | 26 ++++--- .../generated/app_localizations_de.dart | 23 ++++-- .../generated/app_localizations_en.dart | 23 ++++-- .../background.notifications.dart | 17 ++-- .../notifications/pushkeys.notifications.dart | 8 ++ .../notifications/setup.notifications.dart | 2 +- lib/src/utils/misc.dart | 10 +++ lib/src/utils/storage.dart | 10 ++- lib/src/views/chats/add_new_user.view.dart | 2 +- lib/src/views/chats/chat_list.view.dart | 2 +- lib/src/views/chats/chat_messages.view.dart | 2 +- .../all_reactions.bottom_sheet.dart | 4 +- lib/src/views/chats/message_info.view.dart | 2 +- lib/src/views/chats/start_new_chat.view.dart | 2 +- .../components/avatar_icon.component.dart | 78 ++++++++++++------- lib/src/views/contact/contact.view.dart | 2 +- lib/src/views/groups/group.view.dart | 8 +- .../group_create_select_group_name.view.dart | 2 +- .../group_create_select_members.view.dart | 4 +- .../settings/privacy_view_block.users.dart | 2 +- .../views/settings/settings_main.view.dart | 4 +- 30 files changed, 263 insertions(+), 95 deletions(-) diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index 0091f8f..d3e095e 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -215,10 +215,10 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str if systemLanguage.contains("de") { // German title = "Jemand" pushNotificationText = [ - .text: "hat eine Nachricht gesendet.", - .twonly: "hat ein twonly gesendet.", - .video: "hat ein Video gesendet.", - .image: "hat ein Bild gesendet.", + .text: "hat eine Nachricht{inGroup} gesendet.", + .twonly: "hat ein twonly{inGroup} gesendet.", + .video: "hat ein Video{inGroup} gesendet.", + .image: "hat ein Bild{inGroup} gesendet.", .contactRequest: "möchte sich mit dir vernetzen.", .acceptRequest: "ist jetzt mit dir vernetzt.", .storedMediaFile: "hat dein Bild gespeichert.", @@ -228,15 +228,15 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str .reactionToVideo: "hat mit {{content}} auf dein Video reagiert.", .reactionToText: "hat mit {{content}} auf deinen Text reagiert.", .reactionToImage: "hat mit {{content}} auf dein Bild reagiert.", - .response: "hat dir geantwortet.", + .response: "hat dir{inGroup} geantwortet.", .addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt." ] } else { // Default to English pushNotificationText = [ - .text: "has sent you a message.", - .twonly: "has sent you a twonly.", - .video: "has sent you a video.", - .image: "has sent you an image.", + .text: "sent a message{inGroup}.", + .twonly: "sent a twonly{inGroup}.", + .video: "sent a video{inGroup}.", + .image: "sent a image{inGroup}.", .contactRequest: "wants to connect with you.", .acceptRequest: "is now connected with you.", .storedMediaFile: "has stored your image.", @@ -246,7 +246,7 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str .reactionToVideo: "has reacted with {{content}} to your video.", .reactionToText: "has reacted with {{content}} to your text.", .reactionToImage: "has reacted with {{content}} to your image.", - .response: "has responded.", + .response: "has responded{inGroup}.", .addedToGroup: "has added you to \"{{content}}\"" ] } @@ -255,6 +255,8 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str if pushNotification.hasAdditionalContent { content.replace("{{content}}", with: pushNotification.additionalContent) + content.replace("{inGroup}", with: " in {inGroup}") + content.replace("{inGroup}", with: pushNotification.additionalContent) } // Return the corresponding message or an empty string if not found diff --git a/lib/globals.dart b/lib/globals.dart index 36562dd..3bf3b22 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:camera/camera.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/json/userdata.dart'; @@ -26,4 +28,6 @@ void Function() globalCallbackAppIsOutdated = () {}; void Function() globalCallbackNewDeviceRegistered = () {}; void Function(String planId) globalCallbackUpdatePlan = (String planId) {}; +Map globalUserDataChangedCallBack = {}; + bool globalIsAppInBackground = true; diff --git a/lib/main.dart b/lib/main.dart index 9df9558..61644cd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,6 +20,7 @@ import 'package:twonly/src/services/api/mediafiles/media_background.service.dart import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/fcm.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; +import 'package:twonly/src/services/notifications/setup.notifications.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/storage.dart'; @@ -59,6 +60,7 @@ void main() async { unawaited(finishStartedPreprocessing()); unawaited(MediaFileService.purgeTempFolder()); + unawaited(createPushAvatars()); await twonlyDB.messagesDao.purgeMessageTable(); // await twonlyDB.messagesDao.resetPendingDownloadState(); diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index 96ed572..ca20c7e 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -129,8 +129,10 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { leftOuterJoin( groupMembers, groupMembers.contactId.equalsExp(contacts.userId), + useColumns: false, ), ]) + ..orderBy([OrderingTerm.desc(groupMembers.lastMessage)]) ..where(groupMembers.groupId.equals(groupId))); return query.map((row) => row.readTable(contacts)).get(); } @@ -140,8 +142,10 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { leftOuterJoin( groupMembers, groupMembers.contactId.equalsExp(contacts.userId), + useColumns: false, ), ]) + ..orderBy([OrderingTerm.desc(groupMembers.lastMessage)]) ..where(groupMembers.groupId.equals(groupId))); return query.map((row) => row.readTable(contacts)).watch(); } diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index f68163e..5314944 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -381,6 +381,16 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { ), ); + if (message.senderId.present) { + await twonlyDB.groupsDao.updateMember( + message.groupId.value, + message.senderId.value!, + GroupMembersCompanion( + lastMessage: Value(DateTime.now()), + ), + ); + } + return await (select(messages)..where((t) => t.rowId.equals(rowId))) .getSingle(); } catch (e) { diff --git a/lib/src/database/tables/groups.table.dart b/lib/src/database/tables/groups.table.dart index ea25e46..3554306 100644 --- a/lib/src/database/tables/groups.table.dart +++ b/lib/src/database/tables/groups.table.dart @@ -60,6 +60,7 @@ class GroupMembers extends Table { TextColumn get memberState => textEnum().nullable()(); BlobColumn get groupPublicKey => blob().nullable()(); + DateTimeColumn get lastMessage => dateTime().nullable()(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); @override diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index f1589fe..9e58333 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -3987,6 +3987,12 @@ class $GroupMembersTable extends GroupMembers late final GeneratedColumn groupPublicKey = GeneratedColumn('group_public_key', aliasedName, true, type: DriftSqlType.blob, requiredDuringInsert: false); + static const VerificationMeta _lastMessageMeta = + const VerificationMeta('lastMessage'); + @override + late final GeneratedColumn lastMessage = GeneratedColumn( + 'last_message', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override @@ -3997,7 +4003,7 @@ class $GroupMembersTable extends GroupMembers defaultValue: currentDateAndTime); @override List get $columns => - [groupId, contactId, memberState, groupPublicKey, createdAt]; + [groupId, contactId, memberState, groupPublicKey, lastMessage, createdAt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -4026,6 +4032,12 @@ class $GroupMembersTable extends GroupMembers groupPublicKey.isAcceptableOrUnknown( data['group_public_key']!, _groupPublicKeyMeta)); } + if (data.containsKey('last_message')) { + context.handle( + _lastMessageMeta, + lastMessage.isAcceptableOrUnknown( + data['last_message']!, _lastMessageMeta)); + } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); @@ -4048,6 +4060,8 @@ class $GroupMembersTable extends GroupMembers DriftSqlType.string, data['${effectivePrefix}member_state'])), groupPublicKey: attachedDatabase.typeMapping .read(DriftSqlType.blob, data['${effectivePrefix}group_public_key']), + lastMessage: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}last_message']), createdAt: attachedDatabase.typeMapping .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, ); @@ -4070,12 +4084,14 @@ class GroupMember extends DataClass implements Insertable { final int contactId; final MemberState? memberState; final Uint8List? groupPublicKey; + final DateTime? lastMessage; final DateTime createdAt; const GroupMember( {required this.groupId, required this.contactId, this.memberState, this.groupPublicKey, + this.lastMessage, required this.createdAt}); @override Map toColumns(bool nullToAbsent) { @@ -4089,6 +4105,9 @@ class GroupMember extends DataClass implements Insertable { if (!nullToAbsent || groupPublicKey != null) { map['group_public_key'] = Variable(groupPublicKey); } + if (!nullToAbsent || lastMessage != null) { + map['last_message'] = Variable(lastMessage); + } map['created_at'] = Variable(createdAt); return map; } @@ -4103,6 +4122,9 @@ class GroupMember extends DataClass implements Insertable { groupPublicKey: groupPublicKey == null && nullToAbsent ? const Value.absent() : Value(groupPublicKey), + lastMessage: lastMessage == null && nullToAbsent + ? const Value.absent() + : Value(lastMessage), createdAt: Value(createdAt), ); } @@ -4116,6 +4138,7 @@ class GroupMember extends DataClass implements Insertable { memberState: $GroupMembersTable.$convertermemberStaten .fromJson(serializer.fromJson(json['memberState'])), groupPublicKey: serializer.fromJson(json['groupPublicKey']), + lastMessage: serializer.fromJson(json['lastMessage']), createdAt: serializer.fromJson(json['createdAt']), ); } @@ -4128,6 +4151,7 @@ class GroupMember extends DataClass implements Insertable { 'memberState': serializer.toJson( $GroupMembersTable.$convertermemberStaten.toJson(memberState)), 'groupPublicKey': serializer.toJson(groupPublicKey), + 'lastMessage': serializer.toJson(lastMessage), 'createdAt': serializer.toJson(createdAt), }; } @@ -4137,6 +4161,7 @@ class GroupMember extends DataClass implements Insertable { int? contactId, Value memberState = const Value.absent(), Value groupPublicKey = const Value.absent(), + Value lastMessage = const Value.absent(), DateTime? createdAt}) => GroupMember( groupId: groupId ?? this.groupId, @@ -4144,6 +4169,7 @@ class GroupMember extends DataClass implements Insertable { memberState: memberState.present ? memberState.value : this.memberState, groupPublicKey: groupPublicKey.present ? groupPublicKey.value : this.groupPublicKey, + lastMessage: lastMessage.present ? lastMessage.value : this.lastMessage, createdAt: createdAt ?? this.createdAt, ); GroupMember copyWithCompanion(GroupMembersCompanion data) { @@ -4155,6 +4181,8 @@ class GroupMember extends DataClass implements Insertable { groupPublicKey: data.groupPublicKey.present ? data.groupPublicKey.value : this.groupPublicKey, + lastMessage: + data.lastMessage.present ? data.lastMessage.value : this.lastMessage, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ); } @@ -4166,6 +4194,7 @@ class GroupMember extends DataClass implements Insertable { ..write('contactId: $contactId, ') ..write('memberState: $memberState, ') ..write('groupPublicKey: $groupPublicKey, ') + ..write('lastMessage: $lastMessage, ') ..write('createdAt: $createdAt') ..write(')')) .toString(); @@ -4173,7 +4202,7 @@ class GroupMember extends DataClass implements Insertable { @override int get hashCode => Object.hash(groupId, contactId, memberState, - $driftBlobEquality.hash(groupPublicKey), createdAt); + $driftBlobEquality.hash(groupPublicKey), lastMessage, createdAt); @override bool operator ==(Object other) => identical(this, other) || @@ -4183,6 +4212,7 @@ class GroupMember extends DataClass implements Insertable { other.memberState == this.memberState && $driftBlobEquality.equals( other.groupPublicKey, this.groupPublicKey) && + other.lastMessage == this.lastMessage && other.createdAt == this.createdAt); } @@ -4191,6 +4221,7 @@ class GroupMembersCompanion extends UpdateCompanion { final Value contactId; final Value memberState; final Value groupPublicKey; + final Value lastMessage; final Value createdAt; final Value rowid; const GroupMembersCompanion({ @@ -4198,6 +4229,7 @@ class GroupMembersCompanion extends UpdateCompanion { this.contactId = const Value.absent(), this.memberState = const Value.absent(), this.groupPublicKey = const Value.absent(), + this.lastMessage = const Value.absent(), this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); @@ -4206,6 +4238,7 @@ class GroupMembersCompanion extends UpdateCompanion { required int contactId, this.memberState = const Value.absent(), this.groupPublicKey = const Value.absent(), + this.lastMessage = const Value.absent(), this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }) : groupId = Value(groupId), @@ -4215,6 +4248,7 @@ class GroupMembersCompanion extends UpdateCompanion { Expression? contactId, Expression? memberState, Expression? groupPublicKey, + Expression? lastMessage, Expression? createdAt, Expression? rowid, }) { @@ -4223,6 +4257,7 @@ class GroupMembersCompanion extends UpdateCompanion { if (contactId != null) 'contact_id': contactId, if (memberState != null) 'member_state': memberState, if (groupPublicKey != null) 'group_public_key': groupPublicKey, + if (lastMessage != null) 'last_message': lastMessage, if (createdAt != null) 'created_at': createdAt, if (rowid != null) 'rowid': rowid, }); @@ -4233,6 +4268,7 @@ class GroupMembersCompanion extends UpdateCompanion { Value? contactId, Value? memberState, Value? groupPublicKey, + Value? lastMessage, Value? createdAt, Value? rowid}) { return GroupMembersCompanion( @@ -4240,6 +4276,7 @@ class GroupMembersCompanion extends UpdateCompanion { contactId: contactId ?? this.contactId, memberState: memberState ?? this.memberState, groupPublicKey: groupPublicKey ?? this.groupPublicKey, + lastMessage: lastMessage ?? this.lastMessage, createdAt: createdAt ?? this.createdAt, rowid: rowid ?? this.rowid, ); @@ -4261,6 +4298,9 @@ class GroupMembersCompanion extends UpdateCompanion { if (groupPublicKey.present) { map['group_public_key'] = Variable(groupPublicKey.value); } + if (lastMessage.present) { + map['last_message'] = Variable(lastMessage.value); + } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); } @@ -4277,6 +4317,7 @@ class GroupMembersCompanion extends UpdateCompanion { ..write('contactId: $contactId, ') ..write('memberState: $memberState, ') ..write('groupPublicKey: $groupPublicKey, ') + ..write('lastMessage: $lastMessage, ') ..write('createdAt: $createdAt, ') ..write('rowid: $rowid') ..write(')')) @@ -10870,6 +10911,7 @@ typedef $$GroupMembersTableCreateCompanionBuilder = GroupMembersCompanion required int contactId, Value memberState, Value groupPublicKey, + Value lastMessage, Value createdAt, Value rowid, }); @@ -10879,6 +10921,7 @@ typedef $$GroupMembersTableUpdateCompanionBuilder = GroupMembersCompanion Value contactId, Value memberState, Value groupPublicKey, + Value lastMessage, Value createdAt, Value rowid, }); @@ -10935,6 +10978,9 @@ class $$GroupMembersTableFilterComposer column: $table.groupPublicKey, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastMessage => $composableBuilder( + column: $table.lastMessage, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column)); @@ -10995,6 +11041,9 @@ class $$GroupMembersTableOrderingComposer column: $table.groupPublicKey, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastMessage => $composableBuilder( + column: $table.lastMessage, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column)); @@ -11055,6 +11104,9 @@ class $$GroupMembersTableAnnotationComposer GeneratedColumn get groupPublicKey => $composableBuilder( column: $table.groupPublicKey, builder: (column) => column); + GeneratedColumn get lastMessage => $composableBuilder( + column: $table.lastMessage, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); @@ -11126,6 +11178,7 @@ class $$GroupMembersTableTableManager extends RootTableManager< Value contactId = const Value.absent(), Value memberState = const Value.absent(), Value groupPublicKey = const Value.absent(), + Value lastMessage = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => @@ -11134,6 +11187,7 @@ class $$GroupMembersTableTableManager extends RootTableManager< contactId: contactId, memberState: memberState, groupPublicKey: groupPublicKey, + lastMessage: lastMessage, createdAt: createdAt, rowid: rowid, ), @@ -11142,6 +11196,7 @@ class $$GroupMembersTableTableManager extends RootTableManager< required int contactId, Value memberState = const Value.absent(), Value groupPublicKey = const Value.absent(), + Value lastMessage = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => @@ -11150,6 +11205,7 @@ class $$GroupMembersTableTableManager extends RootTableManager< contactId: contactId, memberState: memberState, groupPublicKey: groupPublicKey, + lastMessage: lastMessage, createdAt: createdAt, rowid: rowid, ), diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 8c167fb..e0bcbc9 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -774,10 +774,11 @@ "@twonlySafeRecoverDesc": {}, "twonlySafeRecoverBtn": "Backup wiederherstellen", "@twonlySafeRecoverBtn": {}, - "notificationText": "hat eine Nachricht gesendet.", - "notificationTwonly": "hat ein twonly gesendet.", - "notificationVideo": "hat ein Video gesendet.", - "notificationImage": "hat ein Bild gesendet.", + "notificationFillerIn": "in", + "notificationText": "hat eine Nachricht{inGroup} gesendet.", + "notificationTwonly": "hat ein twonly{inGroup} gesendet.", + "notificationVideo": "hat ein Video{inGroup} gesendet.", + "notificationImage": "hat ein Bild{inGroup} gesendet.", "notificationAddedToGroup": "hat dich zu \"{groupname}\" hinzugefügt.", "notificationContactRequest": "möchte sich mit dir vernetzen.", "notificationAcceptRequest": "ist jetzt mit dir vernetzt.", @@ -787,7 +788,7 @@ "notificationReactionToVideo": "hat mit {reaction} auf dein Video reagiert.", "notificationReactionToText": "hat mit {reaction} auf deine Nachricht reagiert.", "notificationReactionToImage": "hat mit {reaction} auf dein Bild reagiert.", - "notificationResponse": "hat dir geantwortet.", + "notificationResponse": "hat dir{inGroup} geantwortet.", "notificationTitleUnknownUser": "Jemand", "notificationCategoryMessageTitle": "Nachrichten", "notificationCategoryMessageDesc": "Nachrichten von anderen Benutzern." diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index f412f7e..cfb34bc 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -553,10 +553,11 @@ "makerLeftGroup": "{maker} has left the group.", "groupActionYou": "you", "groupActionYour": "your", - "notificationText": "sent a message.", - "notificationTwonly": "sent a twonly.", - "notificationVideo": "sent a video.", - "notificationImage": "sent a image.", + "notificationFillerIn": "in", + "notificationText": "sent a message{inGroup}.", + "notificationTwonly": "sent a twonly{inGroup}.", + "notificationVideo": "sent a video{inGroup}.", + "notificationImage": "sent a image{inGroup}.", "notificationAddedToGroup": "has added you to \"{groupname}\"", "notificationContactRequest": "wants to connect with you.", "notificationAcceptRequest": "is now connected with you.", @@ -566,7 +567,7 @@ "notificationReactionToVideo": "has reacted with {reaction} to your video.", "notificationReactionToText": "has reacted with {reaction} to your message.", "notificationReactionToImage": "has reacted with {reaction} to your image.", - "notificationResponse": "has responded.", + "notificationResponse": "has responded{inGroup}.", "notificationTitleUnknownUser": "Someone", "notificationCategoryMessageTitle": "Messages", "notificationCategoryMessageDesc": "Messages from other users." diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 1b37278..f1bf190 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2420,29 +2420,35 @@ abstract class AppLocalizations { /// **'your'** String get groupActionYour; + /// No description provided for @notificationFillerIn. + /// + /// In en, this message translates to: + /// **'in'** + String get notificationFillerIn; + /// No description provided for @notificationText. /// /// In en, this message translates to: - /// **'sent a message.'** - String get notificationText; + /// **'sent a message{inGroup}.'** + String notificationText(Object inGroup); /// No description provided for @notificationTwonly. /// /// In en, this message translates to: - /// **'sent a twonly.'** - String get notificationTwonly; + /// **'sent a twonly{inGroup}.'** + String notificationTwonly(Object inGroup); /// No description provided for @notificationVideo. /// /// In en, this message translates to: - /// **'sent a video.'** - String get notificationVideo; + /// **'sent a video{inGroup}.'** + String notificationVideo(Object inGroup); /// No description provided for @notificationImage. /// /// In en, this message translates to: - /// **'sent a image.'** - String get notificationImage; + /// **'sent a image{inGroup}.'** + String notificationImage(Object inGroup); /// No description provided for @notificationAddedToGroup. /// @@ -2501,8 +2507,8 @@ abstract class AppLocalizations { /// No description provided for @notificationResponse. /// /// In en, this message translates to: - /// **'has responded.'** - String get notificationResponse; + /// **'has responded{inGroup}.'** + String notificationResponse(Object inGroup); /// No description provided for @notificationTitleUnknownUser. /// diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 0315d9a..b346930 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1310,16 +1310,27 @@ class AppLocalizationsDe extends AppLocalizations { String get groupActionYour => 'deine'; @override - String get notificationText => 'hat eine Nachricht gesendet.'; + String get notificationFillerIn => 'in'; @override - String get notificationTwonly => 'hat ein twonly gesendet.'; + String notificationText(Object inGroup) { + return 'hat eine Nachricht$inGroup gesendet.'; + } @override - String get notificationVideo => 'hat ein Video gesendet.'; + String notificationTwonly(Object inGroup) { + return 'hat ein twonly$inGroup gesendet.'; + } @override - String get notificationImage => 'hat ein Bild gesendet.'; + String notificationVideo(Object inGroup) { + return 'hat ein Video$inGroup gesendet.'; + } + + @override + String notificationImage(Object inGroup) { + return 'hat ein Bild$inGroup gesendet.'; + } @override String notificationAddedToGroup(Object groupname) { @@ -1357,7 +1368,9 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get notificationResponse => 'hat dir geantwortet.'; + String notificationResponse(Object inGroup) { + return 'hat dir$inGroup geantwortet.'; + } @override String get notificationTitleUnknownUser => 'Jemand'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 032ca02..9dff907 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1303,16 +1303,27 @@ class AppLocalizationsEn extends AppLocalizations { String get groupActionYour => 'your'; @override - String get notificationText => 'sent a message.'; + String get notificationFillerIn => 'in'; @override - String get notificationTwonly => 'sent a twonly.'; + String notificationText(Object inGroup) { + return 'sent a message$inGroup.'; + } @override - String get notificationVideo => 'sent a video.'; + String notificationTwonly(Object inGroup) { + return 'sent a twonly$inGroup.'; + } @override - String get notificationImage => 'sent a image.'; + String notificationVideo(Object inGroup) { + return 'sent a video$inGroup.'; + } + + @override + String notificationImage(Object inGroup) { + return 'sent a image$inGroup.'; + } @override String notificationAddedToGroup(Object groupname) { @@ -1350,7 +1361,9 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get notificationResponse => 'has responded.'; + String notificationResponse(Object inGroup) { + return 'has responded$inGroup.'; + } @override String get notificationTitleUnknownUser => 'Someone'; diff --git a/lib/src/services/notifications/background.notifications.dart b/lib/src/services/notifications/background.notifications.dart index f998723..935ea22 100644 --- a/lib/src/services/notifications/background.notifications.dart +++ b/lib/src/services/notifications/background.notifications.dart @@ -237,11 +237,18 @@ AppLocalizations getLocalizations() { String getPushNotificationText(PushNotification pushNotification) { final lang = getLocalizations(); + var inGroup = ''; + + if (pushNotification.hasAdditionalContent()) { + inGroup = + ' ${lang.notificationFillerIn} ${pushNotification.additionalContent}'; + } + final pushNotificationText = { - PushKind.text.name: lang.notificationText, - PushKind.twonly.name: lang.notificationTwonly, - PushKind.video.name: lang.notificationVideo, - PushKind.image.name: lang.notificationImage, + PushKind.text.name: lang.notificationText(inGroup), + PushKind.twonly.name: lang.notificationTwonly(inGroup), + PushKind.video.name: lang.notificationVideo(inGroup), + PushKind.image.name: lang.notificationImage(inGroup), PushKind.contactRequest.name: lang.notificationContactRequest, PushKind.acceptRequest.name: lang.notificationAcceptRequest, PushKind.storedMediaFile.name: lang.notificationStoredMediaFile, @@ -253,7 +260,7 @@ String getPushNotificationText(PushNotification pushNotification) { lang.notificationReactionToText(pushNotification.additionalContent), PushKind.reactionToImage.name: lang.notificationReactionToImage(pushNotification.additionalContent), - PushKind.response.name: lang.notificationResponse, + PushKind.response.name: lang.notificationResponse(inGroup), PushKind.addedToGroup.name: lang.notificationAddedToGroup(pushNotification.additionalContent), }; diff --git a/lib/src/services/notifications/pushkeys.notifications.dart b/lib/src/services/notifications/pushkeys.notifications.dart index 312c3ad..033f2c3 100644 --- a/lib/src/services/notifications/pushkeys.notifications.dart +++ b/lib/src/services/notifications/pushkeys.notifications.dart @@ -234,6 +234,10 @@ Future getPushNotificationFromEncryptedContent( if (content.textMessage.hasQuoteMessageId()) { kind = PushKind.response; } + final group = await twonlyDB.groupsDao.getGroup(content.groupId); + if (group != null && !group.isDirectChat) { + additionalContent = group.groupName; + } } if (content.hasMedia()) { switch (content.media.type) { @@ -248,6 +252,10 @@ Future getPushNotificationFromEncryptedContent( if (content.media.requiresAuthentication) { kind = PushKind.twonly; } + final group = await twonlyDB.groupsDao.getGroup(content.groupId); + if (group != null && !group.isDirectChat) { + additionalContent = group.groupName; + } } if (content.hasContactRequest()) { diff --git a/lib/src/services/notifications/setup.notifications.dart b/lib/src/services/notifications/setup.notifications.dart index 6d9b3b0..1181354 100644 --- a/lib/src/services/notifications/setup.notifications.dart +++ b/lib/src/services/notifications/setup.notifications.dart @@ -62,7 +62,7 @@ Future createPushAvatars() async { final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts(); for (final contact in contacts) { - if (contact.avatarSvgCompressed == null) return; + if (contact.avatarSvgCompressed == null) continue; final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!); diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index 4517571..5a5dd35 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -67,6 +67,16 @@ Uint8List getRandomUint8List(int length) { return randomBytes; } +const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; +Random _rnd = Random(); + +String getRandomString(int length) => String.fromCharCodes( + Iterable.generate( + length, + (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)), + ), + ); + String errorCodeToText(BuildContext context, ErrorCode code) { // ignore: exhaustive_cases switch (code) { diff --git a/lib/src/utils/storage.dart b/lib/src/utils/storage.dart index 119bdd1..1a9f3a5 100644 --- a/lib/src/utils/storage.dart +++ b/lib/src/utils/storage.dart @@ -52,7 +52,7 @@ Mutex updateProtection = Mutex(); Future updateUserdata( UserData Function(UserData userData) updateUser, ) async { - return updateProtection.protect(() async { + final userData = await updateProtection.protect(() async { final user = await getUser(); if (user == null) return null; final updated = updateUser(user); @@ -61,6 +61,14 @@ Future updateUserdata( gUser = updated; return updated; }); + try { + for (final callBack in globalUserDataChangedCallBack.values) { + callBack(); + } + } catch (e) { + Log.error(e); + } + return userData; } Future deleteLocalUserData() async { diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index c24ffec..26db258 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -279,7 +279,7 @@ class ContactsListView extends StatelessWidget { final contact = contacts[index]; return ListTile( title: Text(substringBy(contact.username, 25)), - leading: AvatarIcon(contact: contact), + leading: AvatarIcon(contactId: contact.userId), 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 575300d..1ba8dec 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -123,7 +123,7 @@ class _ChatListViewState extends State { setState(() {}); // gUser has updated }, child: AvatarIcon( - userData: gUser, + myAvatar: true, fontSize: 14, color: context.color.onSurface.withAlpha(20), ), diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index ff8857a..64e5a44 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -386,7 +386,7 @@ class _ChatMessagesViewState extends State { children: messages[i].lastOpenedPosition!.map((w) { return AvatarIcon( key: GlobalKey(), - contact: w, + contactId: w.userId, fontSize: 12, ); }).toList(), diff --git a/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart b/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart index 5292d02..a01b66b 100644 --- a/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart +++ b/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart @@ -116,8 +116,8 @@ class _AllReactionsViewState extends State { child: Row( children: [ AvatarIcon( - contact: entry.$2, - userData: (entry.$2 == null) ? gUser : null, + contactId: entry.$2?.userId, + myAvatar: entry.$2 == null, fontSize: 15, ), const SizedBox(width: 6), diff --git a/lib/src/views/chats/message_info.view.dart b/lib/src/views/chats/message_info.view.dart index c4d81b6..bcf15c8 100644 --- a/lib/src/views/chats/message_info.view.dart +++ b/lib/src/views/chats/message_info.view.dart @@ -127,7 +127,7 @@ class _MessageInfoViewState extends State { child: Row( children: [ AvatarIcon( - contact: groupMember.$2, + contactId: groupMember.$2.userId, fontSize: 15, ), const SizedBox(width: 6), diff --git a/lib/src/views/chats/start_new_chat.view.dart b/lib/src/views/chats/start_new_chat.view.dart index 046e492..3f0f62b 100644 --- a/lib/src/views/chats/start_new_chat.view.dart +++ b/lib/src/views/chats/start_new_chat.view.dart @@ -175,7 +175,7 @@ class UserList extends StatelessWidget { ], ), leading: AvatarIcon( - contact: user, + contactId: user.userId, fontSize: 13, ), onTap: () async { diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/views/components/avatar_icon.component.dart index 2bab8f5..341ba41 100644 --- a/lib/src/views/components/avatar_icon.component.dart +++ b/lib/src/views/components/avatar_icon.component.dart @@ -1,24 +1,22 @@ +import 'dart:async'; 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/misc.dart'; class AvatarIcon extends StatefulWidget { const AvatarIcon({ super.key, this.group, - this.contact, this.contactId, - this.userData, + this.myAvatar = false, this.fontSize = 20, this.color, }); final Group? group; - final Contact? contact; final int? contactId; - final UserData? userData; + final bool myAvatar; final double? fontSize; final Color? color; @@ -27,7 +25,12 @@ class AvatarIcon extends StatefulWidget { } class _AvatarIconState extends State { - final List _avatarSVGs = []; + List _avatarSVGs = []; + String? _globalUserDataCallBackId; + + StreamSubscription>? groupStream; + StreamSubscription>? contactsStream; + StreamSubscription? contactStream; @override void initState() { @@ -35,32 +38,53 @@ class _AvatarIconState extends State { super.initState(); } + @override + void dispose() { + groupStream?.cancel(); + contactStream?.cancel(); + contactsStream?.cancel(); + if (_globalUserDataCallBackId != null) { + globalUserDataChangedCallBack.remove(_globalUserDataCallBackId); + } + super.dispose(); + } + Future initAsync() async { if (widget.group != null) { - final contacts = - await twonlyDB.groupsDao.getGroupContact(widget.group!.groupId); - if (contacts.length == 1) { - if (contacts.first.avatarSvgCompressed != null) { - _avatarSVGs.add(getAvatarSvg(contacts.first.avatarSvgCompressed!)); - } - } else { - for (final contact in contacts) { - if (contact.avatarSvgCompressed != null) { - _avatarSVGs.add(getAvatarSvg(contact.avatarSvgCompressed!)); + groupStream = twonlyDB.groupsDao + .watchGroupContact(widget.group!.groupId) + .listen((contacts) { + _avatarSVGs = []; + if (contacts.length == 1) { + if (contacts.first.avatarSvgCompressed != null) { + _avatarSVGs.add(getAvatarSvg(contacts.first.avatarSvgCompressed!)); + } + } else { + for (final contact in contacts) { + if (contact.avatarSvgCompressed != null) { + _avatarSVGs.add(getAvatarSvg(contact.avatarSvgCompressed!)); + } } } - } - // avatarSvg = group!.avatarSvg; - } else if (widget.userData?.avatarSvg != null) { - _avatarSVGs.add(widget.userData!.avatarSvg!); - } else if (widget.contact?.avatarSvgCompressed != null) { - _avatarSVGs.add(getAvatarSvg(widget.contact!.avatarSvgCompressed!)); + setState(() {}); + }); + } else if (widget.myAvatar) { + _globalUserDataCallBackId = 'avatar_${getRandomString(10)}'; + globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () { + setState(() { + _avatarSVGs = [gUser.avatarSvg!]; + }); + }; + _avatarSVGs.add(gUser.avatarSvg!); } else if (widget.contactId != null) { - final contact = - await twonlyDB.contactsDao.getContactById(widget.contactId!); - if (contact != null && contact.avatarSvgCompressed != null) { - _avatarSVGs.add(getAvatarSvg(contact.avatarSvgCompressed!)); - } + contactStream = twonlyDB.contactsDao + .watchContact(widget.contactId!) + .listen((contact) { + if (contact != null && contact.avatarSvgCompressed != null) { + _avatarSVGs = [getAvatarSvg(contact.avatarSvgCompressed!)]; + setState(() {}); + } + }); } if (mounted) setState(() {}); } diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index b615549..b77ff44 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -115,7 +115,7 @@ class _ContactViewState extends State { children: [ Padding( padding: const EdgeInsets.all(10), - child: AvatarIcon(contact: contact, fontSize: 30), + child: AvatarIcon(contactId: contact.userId, fontSize: 30), ), Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart index 3631f46..72c4461 100644 --- a/lib/src/views/groups/group.view.dart +++ b/lib/src/views/groups/group.view.dart @@ -153,9 +153,8 @@ class _GroupViewState extends State { ), BetterListTile( padding: const EdgeInsets.only(left: 13), - leading: AvatarIcon( - key: GlobalKey(), - userData: gUser, + leading: const AvatarIcon( + myAvatar: true, fontSize: 16, ), text: context.lang.you, @@ -177,8 +176,7 @@ class _GroupViewState extends State { child: BetterListTile( padding: const EdgeInsets.only(left: 13), leading: AvatarIcon( - key: GlobalKey(), - contact: member.$1, + contactId: member.$1.userId, fontSize: 16, ), text: getContactDisplayName(member.$1, maxLength: 25), diff --git a/lib/src/views/groups/group_create_select_group_name.view.dart b/lib/src/views/groups/group_create_select_group_name.view.dart index e6cd4b2..7f280bb 100644 --- a/lib/src/views/groups/group_create_select_group_name.view.dart +++ b/lib/src/views/groups/group_create_select_group_name.view.dart @@ -114,7 +114,7 @@ class _GroupCreateSelectGroupNameViewState ], ), leading: AvatarIcon( - contact: user, + contactId: user.userId, fontSize: 13, ), ), diff --git a/lib/src/views/groups/group_create_select_members.view.dart b/lib/src/views/groups/group_create_select_members.view.dart index f4ffd37..a497622 100644 --- a/lib/src/views/groups/group_create_select_members.view.dart +++ b/lib/src/views/groups/group_create_select_members.view.dart @@ -204,7 +204,7 @@ class _StartNewChatView extends State { ? Text(context.lang.alreadyInGroup) : null, leading: AvatarIcon( - contact: user, + contactId: user.userId, fontSize: 13, ), trailing: Checkbox( @@ -256,7 +256,7 @@ class _Chip extends StatelessWidget { child: Chip( key: GlobalKey(), avatar: AvatarIcon( - contact: contact, + contactId: contact.userId, fontSize: 10, ), label: Row( diff --git a/lib/src/views/settings/privacy_view_block.users.dart b/lib/src/views/settings/privacy_view_block.users.dart index 92066d2..d80f581 100644 --- a/lib/src/views/settings/privacy_view_block.users.dart +++ b/lib/src/views/settings/privacy_view_block.users.dart @@ -112,7 +112,7 @@ class UserList extends StatelessWidget { Text(getContactDisplayName(user)), ], ), - leading: AvatarIcon(contact: user, fontSize: 15), + leading: AvatarIcon(contactId: user.userId, 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 6c818b5..017fd36 100644 --- a/lib/src/views/settings/settings_main.view.dart +++ b/lib/src/views/settings/settings_main.view.dart @@ -55,8 +55,8 @@ class _SettingsMainViewState extends State { color: context.color.surface.withAlpha(0), child: Row( children: [ - AvatarIcon( - userData: gUser, + const AvatarIcon( + myAvatar: true, fontSize: 30, ), Container(width: 20, color: Colors.transparent),