diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index d3e095e..718417c 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -229,7 +229,7 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str .reactionToText: "hat mit {{content}} auf deinen Text reagiert.", .reactionToImage: "hat mit {{content}} auf dein Bild reagiert.", .response: "hat dir{inGroup} geantwortet.", - .addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt." + .addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt.", ] } else { // Default to English pushNotificationText = [ @@ -247,7 +247,7 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str .reactionToText: "has reacted with {{content}} to your text.", .reactionToImage: "has reacted with {{content}} to your image.", .response: "has responded{inGroup}.", - .addedToGroup: "has added you to \"{{content}}\"" + .addedToGroup: "has added you to \"{{content}}\"", ] } @@ -257,9 +257,10 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str content.replace("{{content}}", with: pushNotification.additionalContent) content.replace("{inGroup}", with: " in {inGroup}") content.replace("{inGroup}", with: pushNotification.additionalContent) + } else { + content.replace("{inGroup}", with: "") } // Return the corresponding message or an empty string if not found return (content, title) } - diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index c3920b5..92986e4 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -29,6 +29,10 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { return entry != null; } + Future deleteGroup(String groupId) async { + await (delete(groups)..where((t) => t.groupId.equals(groupId))).go(); + } + Future updateGroup( String groupId, GroupsCompanion updates, @@ -42,7 +46,8 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { ..where( (t) => t.groupId.equals(groupId) & - t.memberState.equals(MemberState.leftGroup.name).not(), + (t.memberState.equals(MemberState.leftGroup.name).not() | + t.memberState.isNull()), )) .get(); } @@ -131,8 +136,9 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { Future _insertGroup(GroupsCompanion group) async { try { - final rowId = await into(groups).insert(group); - return await (select(groups)..where((t) => t.rowId.equals(rowId))) + await into(groups).insert(group); + return await (select(groups) + ..where((t) => t.groupId.equals(group.groupId.value))) .getSingle(); } catch (e) { Log.error('Could not insert group: $e'); diff --git a/lib/src/database/tables/groups.table.dart b/lib/src/database/tables/groups.table.dart index e33cafe..32bbc1c 100644 --- a/lib/src/database/tables/groups.table.dart +++ b/lib/src/database/tables/groups.table.dart @@ -77,6 +77,7 @@ enum GroupActionType { promoteToAdmin, demoteToMember, updatedGroupName, + changeDisplayMaxTime, } @DataClassName('GroupHistory') @@ -94,6 +95,8 @@ class GroupHistories extends Table { TextColumn get oldGroupName => text().nullable()(); TextColumn get newGroupName => text().nullable()(); + IntColumn get newDeleteMessagesAfterMilliseconds => integer().nullable()(); + TextColumn get type => textEnum()(); DateTimeColumn get actionAt => dateTime().withDefault(currentDateAndTime)(); diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index 101d2ba..f3e9a93 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -7038,6 +7038,13 @@ class $GroupHistoriesTable extends GroupHistories late final GeneratedColumn newGroupName = GeneratedColumn( 'new_group_name', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _newDeleteMessagesAfterMillisecondsMeta = + const VerificationMeta('newDeleteMessagesAfterMilliseconds'); + @override + late final GeneratedColumn newDeleteMessagesAfterMilliseconds = + GeneratedColumn( + 'new_delete_messages_after_milliseconds', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); @override late final GeneratedColumnWithTypeConverter type = GeneratedColumn('type', aliasedName, false, @@ -7059,6 +7066,7 @@ class $GroupHistoriesTable extends GroupHistories affectedContactId, oldGroupName, newGroupName, + newDeleteMessagesAfterMilliseconds, type, actionAt ]; @@ -7108,6 +7116,13 @@ class $GroupHistoriesTable extends GroupHistories newGroupName.isAcceptableOrUnknown( data['new_group_name']!, _newGroupNameMeta)); } + if (data.containsKey('new_delete_messages_after_milliseconds')) { + context.handle( + _newDeleteMessagesAfterMillisecondsMeta, + newDeleteMessagesAfterMilliseconds.isAcceptableOrUnknown( + data['new_delete_messages_after_milliseconds']!, + _newDeleteMessagesAfterMillisecondsMeta)); + } if (data.containsKey('action_at')) { context.handle(_actionAtMeta, actionAt.isAcceptableOrUnknown(data['action_at']!, _actionAtMeta)); @@ -7133,6 +7148,9 @@ class $GroupHistoriesTable extends GroupHistories .read(DriftSqlType.string, data['${effectivePrefix}old_group_name']), newGroupName: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}new_group_name']), + newDeleteMessagesAfterMilliseconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}new_delete_messages_after_milliseconds']), type: $GroupHistoriesTable.$convertertype.fromSql(attachedDatabase .typeMapping .read(DriftSqlType.string, data['${effectivePrefix}type'])!), @@ -7157,6 +7175,7 @@ class GroupHistory extends DataClass implements Insertable { final int? affectedContactId; final String? oldGroupName; final String? newGroupName; + final int? newDeleteMessagesAfterMilliseconds; final GroupActionType type; final DateTime actionAt; const GroupHistory( @@ -7166,6 +7185,7 @@ class GroupHistory extends DataClass implements Insertable { this.affectedContactId, this.oldGroupName, this.newGroupName, + this.newDeleteMessagesAfterMilliseconds, required this.type, required this.actionAt}); @override @@ -7185,6 +7205,10 @@ class GroupHistory extends DataClass implements Insertable { if (!nullToAbsent || newGroupName != null) { map['new_group_name'] = Variable(newGroupName); } + if (!nullToAbsent || newDeleteMessagesAfterMilliseconds != null) { + map['new_delete_messages_after_milliseconds'] = + Variable(newDeleteMessagesAfterMilliseconds); + } { map['type'] = Variable($GroupHistoriesTable.$convertertype.toSql(type)); @@ -7209,6 +7233,10 @@ class GroupHistory extends DataClass implements Insertable { newGroupName: newGroupName == null && nullToAbsent ? const Value.absent() : Value(newGroupName), + newDeleteMessagesAfterMilliseconds: + newDeleteMessagesAfterMilliseconds == null && nullToAbsent + ? const Value.absent() + : Value(newDeleteMessagesAfterMilliseconds), type: Value(type), actionAt: Value(actionAt), ); @@ -7224,6 +7252,8 @@ class GroupHistory extends DataClass implements Insertable { affectedContactId: serializer.fromJson(json['affectedContactId']), oldGroupName: serializer.fromJson(json['oldGroupName']), newGroupName: serializer.fromJson(json['newGroupName']), + newDeleteMessagesAfterMilliseconds: + serializer.fromJson(json['newDeleteMessagesAfterMilliseconds']), type: $GroupHistoriesTable.$convertertype .fromJson(serializer.fromJson(json['type'])), actionAt: serializer.fromJson(json['actionAt']), @@ -7239,6 +7269,8 @@ class GroupHistory extends DataClass implements Insertable { 'affectedContactId': serializer.toJson(affectedContactId), 'oldGroupName': serializer.toJson(oldGroupName), 'newGroupName': serializer.toJson(newGroupName), + 'newDeleteMessagesAfterMilliseconds': + serializer.toJson(newDeleteMessagesAfterMilliseconds), 'type': serializer .toJson($GroupHistoriesTable.$convertertype.toJson(type)), 'actionAt': serializer.toJson(actionAt), @@ -7252,6 +7284,7 @@ class GroupHistory extends DataClass implements Insertable { Value affectedContactId = const Value.absent(), Value oldGroupName = const Value.absent(), Value newGroupName = const Value.absent(), + Value newDeleteMessagesAfterMilliseconds = const Value.absent(), GroupActionType? type, DateTime? actionAt}) => GroupHistory( @@ -7265,6 +7298,10 @@ class GroupHistory extends DataClass implements Insertable { oldGroupName.present ? oldGroupName.value : this.oldGroupName, newGroupName: newGroupName.present ? newGroupName.value : this.newGroupName, + newDeleteMessagesAfterMilliseconds: + newDeleteMessagesAfterMilliseconds.present + ? newDeleteMessagesAfterMilliseconds.value + : this.newDeleteMessagesAfterMilliseconds, type: type ?? this.type, actionAt: actionAt ?? this.actionAt, ); @@ -7284,6 +7321,10 @@ class GroupHistory extends DataClass implements Insertable { newGroupName: data.newGroupName.present ? data.newGroupName.value : this.newGroupName, + newDeleteMessagesAfterMilliseconds: + data.newDeleteMessagesAfterMilliseconds.present + ? data.newDeleteMessagesAfterMilliseconds.value + : this.newDeleteMessagesAfterMilliseconds, type: data.type.present ? data.type.value : this.type, actionAt: data.actionAt.present ? data.actionAt.value : this.actionAt, ); @@ -7298,6 +7339,8 @@ class GroupHistory extends DataClass implements Insertable { ..write('affectedContactId: $affectedContactId, ') ..write('oldGroupName: $oldGroupName, ') ..write('newGroupName: $newGroupName, ') + ..write( + 'newDeleteMessagesAfterMilliseconds: $newDeleteMessagesAfterMilliseconds, ') ..write('type: $type, ') ..write('actionAt: $actionAt') ..write(')')) @@ -7305,8 +7348,16 @@ class GroupHistory extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(groupHistoryId, groupId, contactId, - affectedContactId, oldGroupName, newGroupName, type, actionAt); + int get hashCode => Object.hash( + groupHistoryId, + groupId, + contactId, + affectedContactId, + oldGroupName, + newGroupName, + newDeleteMessagesAfterMilliseconds, + type, + actionAt); @override bool operator ==(Object other) => identical(this, other) || @@ -7317,6 +7368,8 @@ class GroupHistory extends DataClass implements Insertable { other.affectedContactId == this.affectedContactId && other.oldGroupName == this.oldGroupName && other.newGroupName == this.newGroupName && + other.newDeleteMessagesAfterMilliseconds == + this.newDeleteMessagesAfterMilliseconds && other.type == this.type && other.actionAt == this.actionAt); } @@ -7328,6 +7381,7 @@ class GroupHistoriesCompanion extends UpdateCompanion { final Value affectedContactId; final Value oldGroupName; final Value newGroupName; + final Value newDeleteMessagesAfterMilliseconds; final Value type; final Value actionAt; final Value rowid; @@ -7338,6 +7392,7 @@ class GroupHistoriesCompanion extends UpdateCompanion { this.affectedContactId = const Value.absent(), this.oldGroupName = const Value.absent(), this.newGroupName = const Value.absent(), + this.newDeleteMessagesAfterMilliseconds = const Value.absent(), this.type = const Value.absent(), this.actionAt = const Value.absent(), this.rowid = const Value.absent(), @@ -7349,6 +7404,7 @@ class GroupHistoriesCompanion extends UpdateCompanion { this.affectedContactId = const Value.absent(), this.oldGroupName = const Value.absent(), this.newGroupName = const Value.absent(), + this.newDeleteMessagesAfterMilliseconds = const Value.absent(), required GroupActionType type, this.actionAt = const Value.absent(), this.rowid = const Value.absent(), @@ -7362,6 +7418,7 @@ class GroupHistoriesCompanion extends UpdateCompanion { Expression? affectedContactId, Expression? oldGroupName, Expression? newGroupName, + Expression? newDeleteMessagesAfterMilliseconds, Expression? type, Expression? actionAt, Expression? rowid, @@ -7373,6 +7430,9 @@ class GroupHistoriesCompanion extends UpdateCompanion { if (affectedContactId != null) 'affected_contact_id': affectedContactId, if (oldGroupName != null) 'old_group_name': oldGroupName, if (newGroupName != null) 'new_group_name': newGroupName, + if (newDeleteMessagesAfterMilliseconds != null) + 'new_delete_messages_after_milliseconds': + newDeleteMessagesAfterMilliseconds, if (type != null) 'type': type, if (actionAt != null) 'action_at': actionAt, if (rowid != null) 'rowid': rowid, @@ -7386,6 +7446,7 @@ class GroupHistoriesCompanion extends UpdateCompanion { Value? affectedContactId, Value? oldGroupName, Value? newGroupName, + Value? newDeleteMessagesAfterMilliseconds, Value? type, Value? actionAt, Value? rowid}) { @@ -7396,6 +7457,8 @@ class GroupHistoriesCompanion extends UpdateCompanion { affectedContactId: affectedContactId ?? this.affectedContactId, oldGroupName: oldGroupName ?? this.oldGroupName, newGroupName: newGroupName ?? this.newGroupName, + newDeleteMessagesAfterMilliseconds: newDeleteMessagesAfterMilliseconds ?? + this.newDeleteMessagesAfterMilliseconds, type: type ?? this.type, actionAt: actionAt ?? this.actionAt, rowid: rowid ?? this.rowid, @@ -7423,6 +7486,10 @@ class GroupHistoriesCompanion extends UpdateCompanion { if (newGroupName.present) { map['new_group_name'] = Variable(newGroupName.value); } + if (newDeleteMessagesAfterMilliseconds.present) { + map['new_delete_messages_after_milliseconds'] = + Variable(newDeleteMessagesAfterMilliseconds.value); + } if (type.present) { map['type'] = Variable( $GroupHistoriesTable.$convertertype.toSql(type.value)); @@ -7445,6 +7512,8 @@ class GroupHistoriesCompanion extends UpdateCompanion { ..write('affectedContactId: $affectedContactId, ') ..write('oldGroupName: $oldGroupName, ') ..write('newGroupName: $newGroupName, ') + ..write( + 'newDeleteMessagesAfterMilliseconds: $newDeleteMessagesAfterMilliseconds, ') ..write('type: $type, ') ..write('actionAt: $actionAt, ') ..write('rowid: $rowid') @@ -13359,6 +13428,7 @@ typedef $$GroupHistoriesTableCreateCompanionBuilder = GroupHistoriesCompanion Value affectedContactId, Value oldGroupName, Value newGroupName, + Value newDeleteMessagesAfterMilliseconds, required GroupActionType type, Value actionAt, Value rowid, @@ -13371,6 +13441,7 @@ typedef $$GroupHistoriesTableUpdateCompanionBuilder = GroupHistoriesCompanion Value affectedContactId, Value oldGroupName, Value newGroupName, + Value newDeleteMessagesAfterMilliseconds, Value type, Value actionAt, Value rowid, @@ -13445,6 +13516,11 @@ class $$GroupHistoriesTableFilterComposer ColumnFilters get newGroupName => $composableBuilder( column: $table.newGroupName, builder: (column) => ColumnFilters(column)); + ColumnFilters get newDeleteMessagesAfterMilliseconds => + $composableBuilder( + column: $table.newDeleteMessagesAfterMilliseconds, + builder: (column) => ColumnFilters(column)); + ColumnWithTypeConverterFilters get type => $composableBuilder( column: $table.type, @@ -13535,6 +13611,11 @@ class $$GroupHistoriesTableOrderingComposer column: $table.newGroupName, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get newDeleteMessagesAfterMilliseconds => + $composableBuilder( + column: $table.newDeleteMessagesAfterMilliseconds, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => $composableBuilder( column: $table.type, builder: (column) => ColumnOrderings(column)); @@ -13620,6 +13701,11 @@ class $$GroupHistoriesTableAnnotationComposer GeneratedColumn get newGroupName => $composableBuilder( column: $table.newGroupName, builder: (column) => column); + GeneratedColumn get newDeleteMessagesAfterMilliseconds => + $composableBuilder( + column: $table.newDeleteMessagesAfterMilliseconds, + builder: (column) => column); + GeneratedColumnWithTypeConverter get type => $composableBuilder(column: $table.type, builder: (column) => column); @@ -13717,6 +13803,8 @@ class $$GroupHistoriesTableTableManager extends RootTableManager< Value affectedContactId = const Value.absent(), Value oldGroupName = const Value.absent(), Value newGroupName = const Value.absent(), + Value newDeleteMessagesAfterMilliseconds = + const Value.absent(), Value type = const Value.absent(), Value actionAt = const Value.absent(), Value rowid = const Value.absent(), @@ -13728,6 +13816,8 @@ class $$GroupHistoriesTableTableManager extends RootTableManager< affectedContactId: affectedContactId, oldGroupName: oldGroupName, newGroupName: newGroupName, + newDeleteMessagesAfterMilliseconds: + newDeleteMessagesAfterMilliseconds, type: type, actionAt: actionAt, rowid: rowid, @@ -13739,6 +13829,8 @@ class $$GroupHistoriesTableTableManager extends RootTableManager< Value affectedContactId = const Value.absent(), Value oldGroupName = const Value.absent(), Value newGroupName = const Value.absent(), + Value newDeleteMessagesAfterMilliseconds = + const Value.absent(), required GroupActionType type, Value actionAt = const Value.absent(), Value rowid = const Value.absent(), @@ -13750,6 +13842,8 @@ class $$GroupHistoriesTableTableManager extends RootTableManager< affectedContactId: affectedContactId, oldGroupName: oldGroupName, newGroupName: newGroupName, + newDeleteMessagesAfterMilliseconds: + newDeleteMessagesAfterMilliseconds, type: type, actionAt: actionAt, rowid: rowid, diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 279201e..3c1f557 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -804,5 +804,7 @@ "leaveGroupSelectOtherAdminBody": "Um die Gruppe zu verlassen, musst du zuerst einen neuen Administrator auswählen.", "leaveGroupSureTitle": "Gruppe verlassen", "leaveGroupSureBody": "Willst du die Gruppe wirklich verlassen?", - "leaveGroupSureOkBtn": "Gruppe verlassen" + "leaveGroupSureOkBtn": "Gruppe verlassen", + "changeDisplayMaxTime": "{username} hat das Zeitlimit für verschwindende Nachrichten auf {time}.", + "youChangedDisplayMaxTime": "Du hat das Zeitlimit für verschwindende Nachrichten auf {time}." } \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 909fa8a..bb5db7b 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -582,5 +582,7 @@ "leaveGroupSelectOtherAdminBody": "To leave the group, you must first select a new administrator.", "leaveGroupSureTitle": "Leave group", "leaveGroupSureBody": "Do you really want to leave the group?", - "leaveGroupSureOkBtn": "Leave group" + "leaveGroupSureOkBtn": "Leave group", + "changeDisplayMaxTime": "{username} has set the time limit for disappearing messages to {time}.", + "youChangedDisplayMaxTime": "You have set the time limit for disappearing messages to {time}." } \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index d238c71..5567323 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2599,6 +2599,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Leave group'** String get leaveGroupSureOkBtn; + + /// No description provided for @changeDisplayMaxTime. + /// + /// In en, this message translates to: + /// **'{username} has set the time limit for disappearing messages to {time}.'** + String changeDisplayMaxTime(Object time, Object username); + + /// No description provided for @youChangedDisplayMaxTime. + /// + /// In en, this message translates to: + /// **'You have set the time limit for disappearing messages to {time}.'** + String youChangedDisplayMaxTime(Object time); } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 37b0129..adc25c5 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1421,4 +1421,14 @@ class AppLocalizationsDe extends AppLocalizations { @override String get leaveGroupSureOkBtn => 'Gruppe verlassen'; + + @override + String changeDisplayMaxTime(Object time, Object username) { + return '$username hat das Zeitlimit für verschwindende Nachrichten auf $time.'; + } + + @override + String youChangedDisplayMaxTime(Object time) { + return 'Du hat das Zeitlimit für verschwindende Nachrichten auf $time.'; + } } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 913445b..19de928 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1411,4 +1411,14 @@ class AppLocalizationsEn extends AppLocalizations { @override String get leaveGroupSureOkBtn => 'Leave group'; + + @override + String changeDisplayMaxTime(Object time, Object username) { + return '$username has set the time limit for disappearing messages to $time.'; + } + + @override + String youChangedDisplayMaxTime(Object time) { + return 'You have set the time limit for disappearing messages to $time.'; + } } diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart index baefcd7..2586ee4 100644 --- a/lib/src/model/protobuf/client/generated/messages.pb.dart +++ b/lib/src/model/protobuf/client/generated/messages.pb.dart @@ -415,6 +415,7 @@ class EncryptedContent_GroupUpdate extends $pb.GeneratedMessage { $core.String? groupActionType, $fixnum.Int64? affectedContactId, $core.String? newGroupName, + $fixnum.Int64? newDeleteMessagesAfterMilliseconds, }) { final $result = create(); if (groupActionType != null) { @@ -426,6 +427,9 @@ class EncryptedContent_GroupUpdate extends $pb.GeneratedMessage { if (newGroupName != null) { $result.newGroupName = newGroupName; } + if (newDeleteMessagesAfterMilliseconds != null) { + $result.newDeleteMessagesAfterMilliseconds = newDeleteMessagesAfterMilliseconds; + } return $result; } EncryptedContent_GroupUpdate._() : super(); @@ -436,6 +440,7 @@ class EncryptedContent_GroupUpdate extends $pb.GeneratedMessage { ..aOS(1, _omitFieldNames ? '' : 'groupActionType', protoName: 'groupActionType') ..aInt64(2, _omitFieldNames ? '' : 'affectedContactId', protoName: 'affectedContactId') ..aOS(3, _omitFieldNames ? '' : 'newGroupName', protoName: 'newGroupName') + ..aInt64(4, _omitFieldNames ? '' : 'newDeleteMessagesAfterMilliseconds', protoName: 'newDeleteMessagesAfterMilliseconds') ..hasRequiredFields = false ; @@ -486,6 +491,15 @@ class EncryptedContent_GroupUpdate extends $pb.GeneratedMessage { $core.bool hasNewGroupName() => $_has(2); @$pb.TagNumber(3) void clearNewGroupName() => clearField(3); + + @$pb.TagNumber(4) + $fixnum.Int64 get newDeleteMessagesAfterMilliseconds => $_getI64(3); + @$pb.TagNumber(4) + set newDeleteMessagesAfterMilliseconds($fixnum.Int64 v) { $_setInt64(3, v); } + @$pb.TagNumber(4) + $core.bool hasNewDeleteMessagesAfterMilliseconds() => $_has(3); + @$pb.TagNumber(4) + void clearNewDeleteMessagesAfterMilliseconds() => clearField(4); } class EncryptedContent_TextMessage extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index fc171fe..37824d4 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -170,10 +170,12 @@ const EncryptedContent_GroupUpdate$json = { {'1': 'groupActionType', '3': 1, '4': 1, '5': 9, '10': 'groupActionType'}, {'1': 'affectedContactId', '3': 2, '4': 1, '5': 3, '9': 0, '10': 'affectedContactId', '17': true}, {'1': 'newGroupName', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'newGroupName', '17': true}, + {'1': 'newDeleteMessagesAfterMilliseconds', '3': 4, '4': 1, '5': 3, '9': 2, '10': 'newDeleteMessagesAfterMilliseconds', '17': true}, ], '8': [ {'1': '_affectedContactId'}, {'1': '_newGroupName'}, + {'1': '_newDeleteMessagesAfterMilliseconds'}, ], }; @@ -389,53 +391,55 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( 'dGVkQ29udGVudC5SZXNlbmRHcm91cFB1YmxpY0tleUgPUhRyZXNlbmRHcm91cFB1YmxpY0tleY' 'gBARpRCgtHcm91cENyZWF0ZRIaCghzdGF0ZUtleRgDIAEoDFIIc3RhdGVLZXkSJgoOZ3JvdXBQ' 'dWJsaWNLZXkYBCABKAxSDmdyb3VwUHVibGljS2V5GjMKCUdyb3VwSm9pbhImCg5ncm91cFB1Ym' - 'xpY0tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkaugEK' + 'xpY0tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkatgIK' 'C0dyb3VwVXBkYXRlEigKD2dyb3VwQWN0aW9uVHlwZRgBIAEoCVIPZ3JvdXBBY3Rpb25UeXBlEj' 'EKEWFmZmVjdGVkQ29udGFjdElkGAIgASgDSABSEWFmZmVjdGVkQ29udGFjdElkiAEBEicKDG5l' - 'd0dyb3VwTmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQFCFAoSX2FmZmVjdGVkQ29udGFjdE' - 'lkQg8KDV9uZXdHcm91cE5hbWUaqQEKC1RleHRNZXNzYWdlEigKD3NlbmRlck1lc3NhZ2VJZBgB' - 'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGA' - 'MgASgDUgl0aW1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBCABKAlIAFIOcXVvdGVNZXNzYWdl' - 'SWSIAQFCEQoPX3F1b3RlTWVzc2FnZUlkGmIKCFJlYWN0aW9uEigKD3RhcmdldE1lc3NhZ2VJZB' - 'gBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEhQKBWVtb2ppGAIgASgJUgVlbW9qaRIWCgZyZW1vdmUY' - 'AyABKAhSBnJlbW92ZRq3AgoNTWVzc2FnZVVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdG' - 'VkQ29udGVudC5NZXNzYWdlVXBkYXRlLlR5cGVSBHR5cGUSLQoPc2VuZGVyTWVzc2FnZUlkGAIg' - 'ASgJSABSD3NlbmRlck1lc3NhZ2VJZIgBARI6ChhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMYAy' - 'ADKAlSGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxIXCgR0ZXh0GAQgASgJSAFSBHRleHSIAQES' - 'HAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXAiLQoEVHlwZRIKCgZERUxFVEUQABINCglFRE' - 'lUX1RFWFQQARIKCgZPUEVORUQQAkISChBfc2VuZGVyTWVzc2FnZUlkQgcKBV90ZXh0GowFCgVN' - 'ZWRpYRIoCg9zZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGA' - 'IgASgOMhwuRW5jcnlwdGVkQ29udGVudC5NZWRpYS5UeXBlUgR0eXBlEkMKGmRpc3BsYXlMaW1p' - 'dEluTWlsbGlzZWNvbmRzGAMgASgDSABSGmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEj' - 'YKFnJlcXVpcmVzQXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24S' - 'HAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBiABKAlIAV' - 'IOcXVvdGVNZXNzYWdlSWSIAQESKQoNZG93bmxvYWRUb2tlbhgHIAEoDEgCUg1kb3dubG9hZFRv' - 'a2VuiAEBEikKDWVuY3J5cHRpb25LZXkYCCABKAxIA1INZW5jcnlwdGlvbktleYgBARIpCg1lbm' - 'NyeXB0aW9uTWFjGAkgASgMSARSDWVuY3J5cHRpb25NYWOIAQESLQoPZW5jcnlwdGlvbk5vbmNl' - 'GAogASgMSAVSD2VuY3J5cHRpb25Ob25jZYgBASIzCgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU' - '1BR0UQARIJCgVWSURFTxACEgcKA0dJRhADQh0KG19kaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25k' - 'c0IRCg9fcXVvdGVNZXNzYWdlSWRCEAoOX2Rvd25sb2FkVG9rZW5CEAoOX2VuY3J5cHRpb25LZX' - 'lCEAoOX2VuY3J5cHRpb25NYWNCEgoQX2VuY3J5cHRpb25Ob25jZRqnAQoLTWVkaWFVcGRhdGUS' - 'NgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVkaWFVcGRhdGUuVHlwZVIEdHlwZR' - 'IoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJF' - 'T1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZX' - 'F1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5' - 'cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIang' - 'IKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFj' - 'dFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0NvbXByZXNzZWQYAiABKAxIAFITYXZhdG' - 'FyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgBARIlCgtk' - 'aXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsKB1JFUVVFU1QQAB' - 'IKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3NlZEILCglfdXNlcm5hbWVCDgoMX2Rp' - 'c3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC' - '5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgASgDSABSBWtleUlkiAEBEhUKA2tleRgD' - 'IAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeX' - 'BlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZfa2V5SWRCBgoEX2tleUIMCgpfY3JlYXRl' - 'ZEF0GocBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudGVyGAEgASgDUgxmbGFtZUNvdW50ZXISNg' - 'oWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIe' - 'CgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kQgoKCF9ncm91cElkQg8KDV9pc0RpcmVjdE' - 'NoYXRCFwoVX3NlbmRlclByb2ZpbGVDb3VudGVyQhAKDl9tZXNzYWdlVXBkYXRlQggKBl9tZWRp' - 'YUIOCgxfbWVkaWFVcGRhdGVCEAoOX2NvbnRhY3RVcGRhdGVCEQoPX2NvbnRhY3RSZXF1ZXN0Qg' - 'wKCl9mbGFtZVN5bmNCCwoJX3B1c2hLZXlzQgsKCV9yZWFjdGlvbkIOCgxfdGV4dE1lc3NhZ2VC' - 'DgoMX2dyb3VwQ3JlYXRlQgwKCl9ncm91cEpvaW5CDgoMX2dyb3VwVXBkYXRlQhcKFV9yZXNlbm' - 'RHcm91cFB1YmxpY0tleQ=='); + 'd0dyb3VwTmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESUwoibmV3RGVsZXRlTWVzc2FnZX' + 'NBZnRlck1pbGxpc2Vjb25kcxgEIAEoA0gCUiJuZXdEZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlz' + 'ZWNvbmRziAEBQhQKEl9hZmZlY3RlZENvbnRhY3RJZEIPCg1fbmV3R3JvdXBOYW1lQiUKI19uZX' + 'dEZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlzZWNvbmRzGqkBCgtUZXh0TWVzc2FnZRIoCg9zZW5k' + 'ZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZXh0Eh' + 'wKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGAQgASgJSABS' + 'DnF1b3RlTWVzc2FnZUlkiAEBQhEKD19xdW90ZU1lc3NhZ2VJZBpiCghSZWFjdGlvbhIoCg90YX' + 'JnZXRNZXNzYWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW1v' + 'amkSFgoGcmVtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIA' + 'EoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRl' + 'ck1lc3NhZ2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYXJnZX' + 'RNZXNzYWdlSWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEo' + 'CUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGRE' + 'VMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIH' + 'CgVfdGV4dBqMBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYW' + 'dlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJD' + 'ChpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbG' + 'xpc2Vjb25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1' + 'dGhlbnRpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2' + 'FnZUlkGAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxI' + 'AlINZG93bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb2' + '5LZXmIAQESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2Vu' + 'Y3J5cHRpb25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQEiMwoEVHlwZRIMCghSRV' + 'VQTE9BRBAAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQA0IdChtfZGlzcGxheUxpbWl0' + 'SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQhAKDl' + '9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2UapwEK' + 'C01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYX' + 'RlLlR5cGVSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQi' + 'NgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAh' + 'p4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250' + 'YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEg' + 'oKBkFDQ0VQVBACGp4CCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRD' + 'b250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRI1ChNhdmF0YXJTdmdDb21wcmVzc2VkGA' + 'IgASgMSABSE2F2YXRhclN2Z0NvbXByZXNzZWSIAQESHwoIdXNlcm5hbWUYAyABKAlIAVIIdXNl' + 'cm5hbWWIAQESJQoLZGlzcGxheU5hbWUYBCABKAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZR' + 'ILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCFgoUX2F2YXRhclN2Z0NvbXByZXNzZWRCCwoJX3Vz' + 'ZXJuYW1lQg4KDF9kaXNwbGF5TmFtZRrVAQoIUHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3' + 'J5cHRlZENvbnRlbnQuUHVzaEtleXMuVHlwZVIEdHlwZRIZCgVrZXlJZBgCIAEoA0gAUgVrZXlJ' + 'ZIgBARIVCgNrZXkYAyABKAxIAVIDa2V5iAEBEiEKCWNyZWF0ZWRBdBgEIAEoA0gCUgljcmVhdG' + 'VkQXSIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCCAoGX2tleUlkQgYKBF9r' + 'ZXlCDAoKX2NyZWF0ZWRBdBqHAQoJRmxhbWVTeW5jEiIKDGZsYW1lQ291bnRlchgBIAEoA1IMZm' + 'xhbWVDb3VudGVyEjYKFmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2UYAiABKANSFmxhc3RGbGFtZUNv' + 'dW50ZXJDaGFuZ2USHgoKYmVzdEZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZEIKCghfZ3JvdXBJZE' + 'IPCg1faXNEaXJlY3RDaGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVw' + 'ZGF0ZUIICgZfbWVkaWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb2' + '50YWN0UmVxdWVzdEIMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoM' + 'X3RleHRNZXNzYWdlQg4KDF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZG' + 'F0ZUIXChVfcmVzZW5kR3JvdXBQdWJsaWNLZXk='); diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto index 96264fc..72b3230 100644 --- a/lib/src/model/protobuf/client/messages.proto +++ b/lib/src/model/protobuf/client/messages.proto @@ -72,6 +72,7 @@ message EncryptedContent { string groupActionType = 1; // GroupActionType.name optional int64 affectedContactId = 2; optional string newGroupName = 3; + optional int64 newDeleteMessagesAfterMilliseconds = 4; } message TextMessage { diff --git a/lib/src/services/api/client2client/groups.c2c.dart b/lib/src/services/api/client2client/groups.c2c.dart index ca5b906..f193911 100644 --- a/lib/src/services/api/client2client/groups.c2c.dart +++ b/lib/src/services/api/client2client/groups.c2c.dart @@ -128,6 +128,16 @@ Future handleGroupUpdate( contactId: Value(fromUserId), ), ); + case GroupActionType.changeDisplayMaxTime: + await twonlyDB.groupsDao.insertGroupAction( + GroupHistoriesCompanion( + groupId: Value(groupId), + type: Value(actionType), + newDeleteMessagesAfterMilliseconds: + Value(update.newDeleteMessagesAfterMilliseconds.toInt()), + contactId: Value(fromUserId), + ), + ); case GroupActionType.removedMember: case GroupActionType.addMember: case GroupActionType.leftGroup: diff --git a/lib/src/services/twonly_safe/common.twonly_safe.dart b/lib/src/services/twonly_safe/common.twonly_safe.dart index 5e022b4..530328a 100644 --- a/lib/src/services/twonly_safe/common.twonly_safe.dart +++ b/lib/src/services/twonly_safe/common.twonly_safe.dart @@ -24,7 +24,7 @@ Future enableTwonlySafe(String password) async { unawaited(performTwonlySafeBackup(force: true)); } -Future disableTwonlySafe() async { +Future removeTwonlySafeFromServer() async { final serverUrl = await getTwonlySafeBackupUrl(); if (serverUrl != null) { try { @@ -40,10 +40,6 @@ Future disableTwonlySafe() async { Log.error('Could not connect to the server.'); } } - await updateUserdata((user) { - user.twonlySafeBackup = null; - return user; - }); } Future<(Uint8List, Uint8List)> getMasterKey( diff --git a/lib/src/views/camera/image_editor/modules/all_emojis.dart b/lib/src/views/camera/image_editor/modules/all_emojis.dart index 3ab1180..7926084 100755 --- a/lib/src/views/camera/image_editor/modules/all_emojis.dart +++ b/lib/src/views/camera/image_editor/modules/all_emojis.dart @@ -26,61 +26,63 @@ class EmojiPickerBottom extends StatelessWidget { ), ], ), - child: Column( - children: [ - Container( - margin: const EdgeInsets.all(30), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(32), - color: Colors.grey, + child: SafeArea( + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + color: Colors.grey, + ), + height: 3, + width: 60, ), - height: 3, - width: 60, - ), - Expanded( - child: EmojiPicker( - onEmojiSelected: (category, emoji) { - Navigator.pop( - context, - EmojiLayerData( - text: emoji.emoji, + Expanded( + child: EmojiPicker( + onEmojiSelected: (category, emoji) { + Navigator.pop( + context, + EmojiLayerData( + text: emoji.emoji, + ), + ); + }, + // textEditingController: _textFieldController, + config: Config( + height: 400, + locale: Localizations.localeOf(context), + viewOrderConfig: const ViewOrderConfig( + top: EmojiPickerItem.searchBar, + // middle: EmojiPickerItem.emojiView, + bottom: EmojiPickerItem.categoryBar, + ), + emojiTextStyle: + TextStyle(fontSize: 24 * (Platform.isIOS ? 1.2 : 1)), + emojiViewConfig: EmojiViewConfig( + backgroundColor: context.color.surfaceContainer, + ), + searchViewConfig: SearchViewConfig( + backgroundColor: context.color.surfaceContainer, + buttonIconColor: Colors.white, + ), + categoryViewConfig: CategoryViewConfig( + backgroundColor: context.color.surfaceContainer, + dividerColor: Colors.white, + indicatorColor: context.color.primary, + iconColorSelected: context.color.primary, + iconColor: context.color.secondary, + ), + bottomActionBarConfig: BottomActionBarConfig( + backgroundColor: context.color.surfaceContainer, + buttonColor: context.color.surfaceContainer, + buttonIconColor: context.color.secondary, ), - ); - }, - // textEditingController: _textFieldController, - config: Config( - height: 400, - locale: Localizations.localeOf(context), - viewOrderConfig: const ViewOrderConfig( - top: EmojiPickerItem.searchBar, - // middle: EmojiPickerItem.emojiView, - bottom: EmojiPickerItem.categoryBar, - ), - emojiTextStyle: - TextStyle(fontSize: 24 * (Platform.isIOS ? 1.2 : 1)), - emojiViewConfig: EmojiViewConfig( - backgroundColor: context.color.surfaceContainer, - ), - searchViewConfig: SearchViewConfig( - backgroundColor: context.color.surfaceContainer, - buttonIconColor: Colors.white, - ), - categoryViewConfig: CategoryViewConfig( - backgroundColor: context.color.surfaceContainer, - dividerColor: Colors.white, - indicatorColor: context.color.primary, - iconColorSelected: context.color.primary, - iconColor: context.color.secondary, - ), - bottomActionBarConfig: BottomActionBarConfig( - backgroundColor: context.color.surfaceContainer, - buttonColor: context.color.surfaceContainer, - buttonIconColor: context.color.secondary, ), ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index cbeffcc..abd8a04 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -114,7 +114,6 @@ class _ChatMessagesViewState extends State { groupActionsSub?.cancel(); lastOpenedMessageByContactSub?.cancel(); tutorial?.cancel(); - textFieldFocus.dispose(); super.dispose(); } 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 a01b66b..8bd31d3 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 @@ -64,7 +64,7 @@ class _AllReactionsViewState extends State { ), ), ); - if (mounted) Navigator.pop(context); + // if (mounted) Navigator.pop(context); } @override diff --git a/lib/src/views/chats/chat_messages_components/chat_group_action.dart b/lib/src/views/chats/chat_messages_components/chat_group_action.dart index f2c2826..e547d6e 100644 --- a/lib/src/views/chats/chat_messages_components/chat_group_action.dart +++ b/lib/src/views/chats/chat_messages_components/chat_group_action.dart @@ -55,6 +55,15 @@ class _ChatGroupActionState extends State { final maker = (contact == null) ? '' : getContactDisplayName(contact!); switch (widget.action.type) { + case GroupActionType.changeDisplayMaxTime: + final time = formatDuration( + context, + (widget.action.newDeleteMessagesAfterMilliseconds ?? 0 / 1000) as int, + ); + text = (contact == null) + ? context.lang.youChangedDisplayMaxTime(time) + : context.lang.changeDisplayMaxTime(maker, time); + icon = FontAwesomeIcons.pencil; case GroupActionType.updatedGroupName: text = (contact == null) ? context.lang.youChangedGroupName(widget.action.newGroupName!) diff --git a/lib/src/views/chats/media_viewer.view.dart b/lib/src/views/chats/media_viewer.view.dart index 5350601..e81347c 100644 --- a/lib/src/views/chats/media_viewer.view.dart +++ b/lib/src/views/chats/media_viewer.view.dart @@ -55,6 +55,7 @@ class _MediaViewerViewState extends State { bool imageSaved = false; bool imageSaving = false; bool displayTwonlyPresent = true; + final emojiKey = GlobalKey(); StreamSubscription? downloadStateListener; @@ -634,6 +635,7 @@ class _MediaViewerViewState extends State { mediaViewerDistanceFromBottom: mediaViewerDistanceFromBottom, groupId: widget.group.groupId, messageId: currentMessage!.messageId, + emojiKey: emojiKey, hide: () { setState(() { showShortReactions = false; @@ -641,6 +643,9 @@ class _MediaViewerViewState extends State { }); }, ), + Positioned.fill( + child: EmojiFloatWidget(key: emojiKey), + ), ], ), ), 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 index d6fc8d3..2f3a871 100644 --- 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 @@ -1,11 +1,46 @@ -// ignore_for_file: avoid_dynamic_calls - import 'package:flutter/material.dart'; import 'package:twonly/globals.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/chats/media_viewer_components/reaction_buttons.component.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; +Offset getGlobalOffset(GlobalKey targetKey) { + final ctx = targetKey.currentContext; + if (ctx == null) { + return Offset.zero; + } + final renderObject = ctx.findRenderObject(); + if (renderObject is RenderBox) { + return renderObject.localToGlobal( + Offset(renderObject.size.width / 2, renderObject.size.height / 2), + ); + } + return Offset.zero; +} + +Future sendReaction( + String groupId, + String messageId, + String emoji, +) async { + await twonlyDB.reactionsDao.updateMyReaction( + messageId, + emoji, + false, + ); + await sendCipherTextToGroup( + groupId, + EncryptedContent( + reaction: EncryptedContent_Reaction( + targetMessageId: messageId, + emoji: emoji, + remove: false, + ), + ), + ); +} + class EmojiReactionWidget extends StatefulWidget { const EmojiReactionWidget({ required this.messageId, @@ -13,74 +48,46 @@ class EmojiReactionWidget extends StatefulWidget { required this.hide, required this.show, required this.emoji, + required this.emojiKey, super.key, }); final String messageId; final String groupId; - final Function hide; + final void Function() hide; final bool show; final String emoji; + final GlobalKey emojiKey; @override State createState() => _EmojiReactionWidgetState(); } class _EmojiReactionWidgetState extends State { - int selectedShortReaction = -1; + final GlobalKey _targetKey = GlobalKey(); @override Widget build(BuildContext context) { return AnimatedSize( + key: _targetKey, duration: const Duration(milliseconds: 200), curve: Curves.linearToEaseOut, child: GestureDetector( onTap: () async { - await twonlyDB.reactionsDao - .updateMyReaction(widget.messageId, widget.emoji, false); - - await sendCipherTextToGroup( - widget.groupId, - EncryptedContent( - reaction: EncryptedContent_Reaction( - targetMessageId: widget.messageId, - emoji: widget.emoji, - remove: false, - ), - ), + await sendReaction(widget.groupId, widget.messageId, widget.emoji); + widget.emojiKey.currentState?.spawn( + getGlobalOffset(_targetKey), + 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; - }); - } - }); + widget.hide(); }, - 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, - ), - ), - ), - ), + 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 index d5678fa..5da9f3d 100644 --- a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart +++ b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart @@ -1,6 +1,12 @@ import 'dart:async'; +import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; +import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart'; import 'package:twonly/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; @@ -11,6 +17,7 @@ class ReactionButtons extends StatefulWidget { required this.mediaViewerDistanceFromBottom, required this.messageId, required this.groupId, + required this.emojiKey, required this.hide, super.key, }); @@ -18,6 +25,7 @@ class ReactionButtons extends StatefulWidget { final double mediaViewerDistanceFromBottom; final bool show; final bool textInputFocused; + final GlobalKey emojiKey; final String messageId; final String groupId; final void Function() hide; @@ -28,6 +36,7 @@ class ReactionButtons extends StatefulWidget { class _ReactionButtonsState extends State { int selectedShortReaction = -1; + final GlobalKey _keyEmojiPicker = GlobalKey(); List selectedEmojis = EmojiAnimation.animatedIcons.keys.toList().sublist(0, 6); @@ -82,6 +91,7 @@ class _ReactionButtonsState extends State { hide: widget.hide, show: widget.show, emoji: emoji as String, + emojiKey: widget.emojiKey, ), ) .toList(), @@ -90,17 +100,53 @@ class _ReactionButtonsState extends State { 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, + children: [ + ...firstRowEmojis.map( + (emoji) => EmojiReactionWidget( + messageId: widget.messageId, + groupId: widget.groupId, + hide: widget.hide, + show: widget.show, + emoji: emoji, + emojiKey: widget.emojiKey, + ), + ), + GestureDetector( + key: _keyEmojiPicker, + onTap: () async { + // ignore: inference_failure_on_function_invocation + final layer = await showModalBottomSheet( + context: context, + backgroundColor: context.color.surface, + builder: (BuildContext context) { + return const EmojiPickerBottom(); + }, + ) as EmojiLayerData?; + if (layer == null) return; + await sendReaction( + widget.groupId, + widget.messageId, + layer.text, + ); + widget.emojiKey.currentState?.spawn( + getGlobalOffset(_keyEmojiPicker), + layer.text, + ); + widget.hide(); + }, + child: Container( + decoration: BoxDecoration( + color: context.color.surfaceContainer.withAlpha(100), + borderRadius: BorderRadius.circular(12), ), - ) - .toList(), + padding: const EdgeInsets.all(8), + child: const FaIcon( + FontAwesomeIcons.ellipsisVertical, + size: 24, + ), + ), + ), + ], ), ], ), @@ -109,3 +155,164 @@ class _ReactionButtonsState extends State { ); } } + +class EmojiFloatWidget extends StatefulWidget { + const EmojiFloatWidget({ + super.key, + }); + @override + EmojiFloatWidgetState createState() => EmojiFloatWidgetState(); +} + +class EmojiFloatWidgetState extends State + with SingleTickerProviderStateMixin { + final List<_Particle> _particles = []; + late final Ticker _ticker; + final Random _rnd = Random(); + Duration _lastTick = Duration.zero; + + @override + void initState() { + super.initState(); + _ticker = createTicker(_tick)..start(); + } + + void _tick(Duration elapsed) { + final dt = (_lastTick == Duration.zero) + ? 0.016 + : (elapsed - _lastTick).inMicroseconds / 1e6; + _lastTick = elapsed; + + for (final p in List<_Particle>.from(_particles)) { + p.update(dt); + if (p.isDead) _particles.remove(p); + } + if (mounted) setState(() {}); + } + + @override + void dispose() { + _ticker.dispose(); + super.dispose(); + } + + /// Call this to spawn the emoji animation from a global screen position. + void spawn(Offset globalPosition, String emoji) { + final box = context.findRenderObject() as RenderBox?; + if (box == null) return; + final local = box.globalToLocal(globalPosition); + const spawnCount = 10; + final life = const Duration(milliseconds: 2000).inMilliseconds / 1000.0; + + for (var i = 0; i < spawnCount; i++) { + final dx = (_rnd.nextDouble() - 0.5) * 220; + final vx = dx; + final vy = -(100 + _rnd.nextDouble() * 80); + final rot = (_rnd.nextDouble() - 0.5) * 2; + final scale = 0.9 + _rnd.nextDouble() * 0.6; + + _particles.add( + _Particle( + emoji: emoji, + x: local.dx, + y: local.dy, + vx: vx, + vy: vy, + rotation: rot, + lifetime: life, + scale: scale, + ), + ); + } + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: CustomPaint( + painter: _ParticlePainter(List<_Particle>.from(_particles)), + size: Size.infinite, + ), + ); + } +} + +class _Particle { + _Particle({ + required this.emoji, + required this.x, + required this.y, + required this.vx, + required this.vy, + required this.rotation, + required this.lifetime, + required this.scale, + }); + final String emoji; + double x; + double y; + double vx; + double vy; + double rotation; + double age = 0; + final double lifetime; + final double scale; + bool get isDead => age >= lifetime; + + void update(double dt) { + age += dt; + // vertical-only motion emphasis: mild gravity slows ascent then gently pulls down + vy += 100 * dt; // gravity (positive = down) + // slight horizontal drag to reduce sideways drift + vx *= 1 - 3.0 * dt; + // integrate position + x += vx * dt; + y += vy * dt; + // slow rotation decay + rotation *= 1 - 1.5 * dt; + } + + double get progress => (age / lifetime).clamp(0.0, 1.0); + + // opacity falls from 1 -> 0 as particle ages + double get opacity => (1.0 - progress).clamp(0.0, 1.0); + + // scale can gently grow then shrink; here we slightly increase early + double get currentScale { + final p = progress; + if (p < 0.5) return scale * (1.0 + 0.3 * (p / 0.5)); + return scale * (1.3 - 0.3 * ((p - 0.5) / 0.5)); + } +} + +class _ParticlePainter extends CustomPainter { + _ParticlePainter(this.particles); + final List<_Particle> particles; + + @override + void paint(Canvas canvas, Size size) { + final textPainter = TextPainter(textDirection: TextDirection.ltr); + for (final p in particles) { + final tp = TextSpan( + text: p.emoji, + style: TextStyle( + fontSize: 24 * p.currentScale, + color: Colors.black.withValues(alpha: p.opacity), + ), + ); + textPainter + ..text = tp + ..layout(); + canvas + ..save() + ..translate(p.x - textPainter.width / 2, p.y - textPainter.height / 2) + ..rotate(p.rotation); + textPainter.paint(canvas, Offset.zero); + canvas.restore(); + } + } + + @override + bool shouldRepaint(covariant _ParticlePainter old) => true; +} diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/views/components/avatar_icon.component.dart index f82bbbc..0eca6dd 100644 --- a/lib/src/views/components/avatar_icon.component.dart +++ b/lib/src/views/components/avatar_icon.component.dart @@ -72,10 +72,16 @@ class _AvatarIconState extends State { _globalUserDataCallBackId = 'avatar_${getRandomString(10)}'; globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () { setState(() { - _avatarSVGs = [gUser.avatarSvg!]; + if (gUser.avatarSvg != null) { + _avatarSVGs = [gUser.avatarSvg!]; + } else { + _avatarSVGs = []; + } }); }; - _avatarSVGs.add(gUser.avatarSvg!); + if (gUser.avatarSvg != null) { + _avatarSVGs = [gUser.avatarSvg!]; + } } else if (widget.contactId != null) { contactStream = twonlyDB.contactsDao .watchContact(widget.contactId!) diff --git a/lib/src/views/components/group_context_menu.component.dart b/lib/src/views/components/group_context_menu.component.dart index 6dca019..45af6a7 100644 --- a/lib/src/views/components/group_context_menu.component.dart +++ b/lib/src/views/components/group_context_menu.component.dart @@ -82,13 +82,14 @@ class GroupContextMenu extends StatelessWidget { context.lang.groupContextMenuDeleteGroup, ); if (ok) { - await twonlyDB.messagesDao.deleteMessagesByGroupId(group.groupId); - await twonlyDB.groupsDao.updateGroup( - group.groupId, - const GroupsCompanion( - deletedContent: Value(true), - ), - ); + // await twonlyDB.messagesDao.deleteMessagesByGroupId(group.groupId); + await twonlyDB.groupsDao.deleteGroup(group.groupId); + // await twonlyDB.groupsDao.updateGroup( + // group.groupId, + // const GroupsCompanion( + // deletedContent: Value(true), + // ), + // ); } }, ), diff --git a/lib/src/views/settings/profile/profile.view.dart b/lib/src/views/settings/profile/profile.view.dart index 56eefe5..02aa643 100644 --- a/lib/src/views/settings/profile/profile.view.dart +++ b/lib/src/views/settings/profile/profile.view.dart @@ -6,6 +6,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart'; +import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/better_list_title.dart'; @@ -59,6 +61,10 @@ class _ProfileViewState extends State { return; } + // as the username has changes, remove the old from the server and then upload it again. + await removeTwonlySafeFromServer(); + unawaited(performTwonlySafeBackup(force: true)); + await updateUserdata((user) { user ..username = username