diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index ca20c7e..1d930a0 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -164,7 +164,11 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { } Stream> watchGroupsForShareImage() { - return (select(groups)..where((g) => g.leftGroup.equals(false))).watch(); + return (select(groups) + ..where( + (g) => g.leftGroup.equals(false) & g.deletedContent.equals(false), + )) + .watch(); } Stream watchGroup(String groupId) { @@ -174,10 +178,18 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { Stream> watchGroupsForChatList() { return (select(groups) + ..where((t) => t.deletedContent.equals(false)) ..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)])) .watch(); } + Stream> watchGroupsForStartNewChat() { + return (select(groups) + ..where((t) => t.isDirectChat.equals(false)) + ..orderBy([(t) => OrderingTerm.asc(t.groupName)])) + .watch(); + } + Future getGroup(String groupId) { return (select(groups)..where((t) => t.groupId.equals(groupId))) .getSingleOrNull(); diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index 4e45db6..7c7fa35 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -380,6 +380,7 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { GroupsCompanion( lastMessageExchange: Value(DateTime.now()), archived: const Value(false), + deletedContent: const Value(false), ), ); @@ -468,6 +469,10 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { return (delete(messages)..where((t) => t.messageId.equals(messageId))).go(); } + Future deleteMessagesByGroupId(String groupId) { + return (delete(messages)..where((t) => t.groupId.equals(groupId))).go(); + } + // Future deleteAllMessagesByContactId(int contactId) { // return (delete(messages)..where((t) => t.contactId.equals(contactId))).go(); // } diff --git a/lib/src/database/tables/groups.table.dart b/lib/src/database/tables/groups.table.dart index 3554306..e33cafe 100644 --- a/lib/src/database/tables/groups.table.dart +++ b/lib/src/database/tables/groups.table.dart @@ -14,6 +14,8 @@ class Groups extends Table { BoolColumn get joinedGroup => boolean().withDefault(const Constant(false))(); BoolColumn get leftGroup => boolean().withDefault(const Constant(false))(); + BoolColumn get deletedContent => + boolean().withDefault(const Constant(false))(); IntColumn get stateVersionId => integer().withDefault(const Constant(0))(); diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index 2e07136..101d2ba 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -724,6 +724,16 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> { defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("left_group" IN (0, 1))'), defaultValue: const Constant(false)); + static const VerificationMeta _deletedContentMeta = + const VerificationMeta('deletedContent'); + @override + late final GeneratedColumn deletedContent = GeneratedColumn( + 'deleted_content', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("deleted_content" IN (0, 1))'), + defaultValue: const Constant(false)); static const VerificationMeta _stateVersionIdMeta = const VerificationMeta('stateVersionId'); @override @@ -848,6 +858,7 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> { archived, joinedGroup, leftGroup, + deletedContent, stateVersionId, stateEncryptionKey, myGroupPrivateKey, @@ -911,6 +922,12 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> { context.handle(_leftGroupMeta, leftGroup.isAcceptableOrUnknown(data['left_group']!, _leftGroupMeta)); } + if (data.containsKey('deleted_content')) { + context.handle( + _deletedContentMeta, + deletedContent.isAcceptableOrUnknown( + data['deleted_content']!, _deletedContentMeta)); + } if (data.containsKey('state_version_id')) { context.handle( _stateVersionIdMeta, @@ -1029,6 +1046,8 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> { .read(DriftSqlType.bool, data['${effectivePrefix}joined_group'])!, leftGroup: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}left_group'])!, + deletedContent: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}deleted_content'])!, stateVersionId: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}state_version_id'])!, stateEncryptionKey: attachedDatabase.typeMapping.read( @@ -1083,6 +1102,7 @@ class Group extends DataClass implements Insertable { final bool archived; final bool joinedGroup; final bool leftGroup; + final bool deletedContent; final int stateVersionId; final Uint8List? stateEncryptionKey; final Uint8List? myGroupPrivateKey; @@ -1107,6 +1127,7 @@ class Group extends DataClass implements Insertable { required this.archived, required this.joinedGroup, required this.leftGroup, + required this.deletedContent, required this.stateVersionId, this.stateEncryptionKey, this.myGroupPrivateKey, @@ -1133,6 +1154,7 @@ class Group extends DataClass implements Insertable { map['archived'] = Variable(archived); map['joined_group'] = Variable(joinedGroup); map['left_group'] = Variable(leftGroup); + map['deleted_content'] = Variable(deletedContent); map['state_version_id'] = Variable(stateVersionId); if (!nullToAbsent || stateEncryptionKey != null) { map['state_encryption_key'] = Variable(stateEncryptionKey); @@ -1177,6 +1199,7 @@ class Group extends DataClass implements Insertable { archived: Value(archived), joinedGroup: Value(joinedGroup), leftGroup: Value(leftGroup), + deletedContent: Value(deletedContent), stateVersionId: Value(stateVersionId), stateEncryptionKey: stateEncryptionKey == null && nullToAbsent ? const Value.absent() @@ -1221,6 +1244,7 @@ class Group extends DataClass implements Insertable { archived: serializer.fromJson(json['archived']), joinedGroup: serializer.fromJson(json['joinedGroup']), leftGroup: serializer.fromJson(json['leftGroup']), + deletedContent: serializer.fromJson(json['deletedContent']), stateVersionId: serializer.fromJson(json['stateVersionId']), stateEncryptionKey: serializer.fromJson(json['stateEncryptionKey']), @@ -1257,6 +1281,7 @@ class Group extends DataClass implements Insertable { 'archived': serializer.toJson(archived), 'joinedGroup': serializer.toJson(joinedGroup), 'leftGroup': serializer.toJson(leftGroup), + 'deletedContent': serializer.toJson(deletedContent), 'stateVersionId': serializer.toJson(stateVersionId), 'stateEncryptionKey': serializer.toJson(stateEncryptionKey), 'myGroupPrivateKey': serializer.toJson(myGroupPrivateKey), @@ -1286,6 +1311,7 @@ class Group extends DataClass implements Insertable { bool? archived, bool? joinedGroup, bool? leftGroup, + bool? deletedContent, int? stateVersionId, Value stateEncryptionKey = const Value.absent(), Value myGroupPrivateKey = const Value.absent(), @@ -1310,6 +1336,7 @@ class Group extends DataClass implements Insertable { archived: archived ?? this.archived, joinedGroup: joinedGroup ?? this.joinedGroup, leftGroup: leftGroup ?? this.leftGroup, + deletedContent: deletedContent ?? this.deletedContent, stateVersionId: stateVersionId ?? this.stateVersionId, stateEncryptionKey: stateEncryptionKey.present ? stateEncryptionKey.value @@ -1355,6 +1382,9 @@ class Group extends DataClass implements Insertable { joinedGroup: data.joinedGroup.present ? data.joinedGroup.value : this.joinedGroup, leftGroup: data.leftGroup.present ? data.leftGroup.value : this.leftGroup, + deletedContent: data.deletedContent.present + ? data.deletedContent.value + : this.deletedContent, stateVersionId: data.stateVersionId.present ? data.stateVersionId.value : this.stateVersionId, @@ -1413,6 +1443,7 @@ class Group extends DataClass implements Insertable { ..write('archived: $archived, ') ..write('joinedGroup: $joinedGroup, ') ..write('leftGroup: $leftGroup, ') + ..write('deletedContent: $deletedContent, ') ..write('stateVersionId: $stateVersionId, ') ..write('stateEncryptionKey: $stateEncryptionKey, ') ..write('myGroupPrivateKey: $myGroupPrivateKey, ') @@ -1443,6 +1474,7 @@ class Group extends DataClass implements Insertable { archived, joinedGroup, leftGroup, + deletedContent, stateVersionId, $driftBlobEquality.hash(stateEncryptionKey), $driftBlobEquality.hash(myGroupPrivateKey), @@ -1471,6 +1503,7 @@ class Group extends DataClass implements Insertable { other.archived == this.archived && other.joinedGroup == this.joinedGroup && other.leftGroup == this.leftGroup && + other.deletedContent == this.deletedContent && other.stateVersionId == this.stateVersionId && $driftBlobEquality.equals( other.stateEncryptionKey, this.stateEncryptionKey) && @@ -1500,6 +1533,7 @@ class GroupsCompanion extends UpdateCompanion { final Value archived; final Value joinedGroup; final Value leftGroup; + final Value deletedContent; final Value stateVersionId; final Value stateEncryptionKey; final Value myGroupPrivateKey; @@ -1525,6 +1559,7 @@ class GroupsCompanion extends UpdateCompanion { this.archived = const Value.absent(), this.joinedGroup = const Value.absent(), this.leftGroup = const Value.absent(), + this.deletedContent = const Value.absent(), this.stateVersionId = const Value.absent(), this.stateEncryptionKey = const Value.absent(), this.myGroupPrivateKey = const Value.absent(), @@ -1551,6 +1586,7 @@ class GroupsCompanion extends UpdateCompanion { this.archived = const Value.absent(), this.joinedGroup = const Value.absent(), this.leftGroup = const Value.absent(), + this.deletedContent = const Value.absent(), this.stateVersionId = const Value.absent(), this.stateEncryptionKey = const Value.absent(), this.myGroupPrivateKey = const Value.absent(), @@ -1578,6 +1614,7 @@ class GroupsCompanion extends UpdateCompanion { Expression? archived, Expression? joinedGroup, Expression? leftGroup, + Expression? deletedContent, Expression? stateVersionId, Expression? stateEncryptionKey, Expression? myGroupPrivateKey, @@ -1604,6 +1641,7 @@ class GroupsCompanion extends UpdateCompanion { if (archived != null) 'archived': archived, if (joinedGroup != null) 'joined_group': joinedGroup, if (leftGroup != null) 'left_group': leftGroup, + if (deletedContent != null) 'deleted_content': deletedContent, if (stateVersionId != null) 'state_version_id': stateVersionId, if (stateEncryptionKey != null) 'state_encryption_key': stateEncryptionKey, @@ -1638,6 +1676,7 @@ class GroupsCompanion extends UpdateCompanion { Value? archived, Value? joinedGroup, Value? leftGroup, + Value? deletedContent, Value? stateVersionId, Value? stateEncryptionKey, Value? myGroupPrivateKey, @@ -1663,6 +1702,7 @@ class GroupsCompanion extends UpdateCompanion { archived: archived ?? this.archived, joinedGroup: joinedGroup ?? this.joinedGroup, leftGroup: leftGroup ?? this.leftGroup, + deletedContent: deletedContent ?? this.deletedContent, stateVersionId: stateVersionId ?? this.stateVersionId, stateEncryptionKey: stateEncryptionKey ?? this.stateEncryptionKey, myGroupPrivateKey: myGroupPrivateKey ?? this.myGroupPrivateKey, @@ -1709,6 +1749,9 @@ class GroupsCompanion extends UpdateCompanion { if (leftGroup.present) { map['left_group'] = Variable(leftGroup.value); } + if (deletedContent.present) { + map['deleted_content'] = Variable(deletedContent.value); + } if (stateVersionId.present) { map['state_version_id'] = Variable(stateVersionId.value); } @@ -1780,6 +1823,7 @@ class GroupsCompanion extends UpdateCompanion { ..write('archived: $archived, ') ..write('joinedGroup: $joinedGroup, ') ..write('leftGroup: $leftGroup, ') + ..write('deletedContent: $deletedContent, ') ..write('stateVersionId: $stateVersionId, ') ..write('stateEncryptionKey: $stateEncryptionKey, ') ..write('myGroupPrivateKey: $myGroupPrivateKey, ') @@ -8333,6 +8377,7 @@ typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({ Value archived, Value joinedGroup, Value leftGroup, + Value deletedContent, Value stateVersionId, Value stateEncryptionKey, Value myGroupPrivateKey, @@ -8359,6 +8404,7 @@ typedef $$GroupsTableUpdateCompanionBuilder = GroupsCompanion Function({ Value archived, Value joinedGroup, Value leftGroup, + Value deletedContent, Value stateVersionId, Value stateEncryptionKey, Value myGroupPrivateKey, @@ -8459,6 +8505,10 @@ class $$GroupsTableFilterComposer extends Composer<_$TwonlyDB, $GroupsTable> { ColumnFilters get leftGroup => $composableBuilder( column: $table.leftGroup, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedContent => $composableBuilder( + column: $table.deletedContent, + builder: (column) => ColumnFilters(column)); + ColumnFilters get stateVersionId => $composableBuilder( column: $table.stateVersionId, builder: (column) => ColumnFilters(column)); @@ -8614,6 +8664,10 @@ class $$GroupsTableOrderingComposer extends Composer<_$TwonlyDB, $GroupsTable> { ColumnOrderings get leftGroup => $composableBuilder( column: $table.leftGroup, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedContent => $composableBuilder( + column: $table.deletedContent, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get stateVersionId => $composableBuilder( column: $table.stateVersionId, builder: (column) => ColumnOrderings(column)); @@ -8708,6 +8762,9 @@ class $$GroupsTableAnnotationComposer GeneratedColumn get leftGroup => $composableBuilder(column: $table.leftGroup, builder: (column) => column); + GeneratedColumn get deletedContent => $composableBuilder( + column: $table.deletedContent, builder: (column) => column); + GeneratedColumn get stateVersionId => $composableBuilder( column: $table.stateVersionId, builder: (column) => column); @@ -8853,6 +8910,7 @@ class $$GroupsTableTableManager extends RootTableManager< Value archived = const Value.absent(), Value joinedGroup = const Value.absent(), Value leftGroup = const Value.absent(), + Value deletedContent = const Value.absent(), Value stateVersionId = const Value.absent(), Value stateEncryptionKey = const Value.absent(), Value myGroupPrivateKey = const Value.absent(), @@ -8879,6 +8937,7 @@ class $$GroupsTableTableManager extends RootTableManager< archived: archived, joinedGroup: joinedGroup, leftGroup: leftGroup, + deletedContent: deletedContent, stateVersionId: stateVersionId, stateEncryptionKey: stateEncryptionKey, myGroupPrivateKey: myGroupPrivateKey, @@ -8905,6 +8964,7 @@ class $$GroupsTableTableManager extends RootTableManager< Value archived = const Value.absent(), Value joinedGroup = const Value.absent(), Value leftGroup = const Value.absent(), + Value deletedContent = const Value.absent(), Value stateVersionId = const Value.absent(), Value stateEncryptionKey = const Value.absent(), Value myGroupPrivateKey = const Value.absent(), @@ -8931,6 +8991,7 @@ class $$GroupsTableTableManager extends RootTableManager< archived: archived, joinedGroup: joinedGroup, leftGroup: leftGroup, + deletedContent: deletedContent, stateVersionId: stateVersionId, stateEncryptionKey: stateEncryptionKey, myGroupPrivateKey: myGroupPrivateKey, diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index e0bcbc9..008081d 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -80,6 +80,7 @@ "@shareImageUserNotVerifiedDesc": {}, "shareImageShowArchived": "Archivierte Benutzer anzeigen", "@shareImageShowArchived": {}, + "startNewChatSearchHint": "Name, Benutzername oder Gruppenname", "searchUsernameInput": "Benutzername", "@searchUsernameInput": {}, "searchUsernameTitle": "Benutzernamen suchen", @@ -692,6 +693,9 @@ "@durationShortHour": {}, "durationShortDays": "Tagen", "@durationShortDays": {}, + "contacts": "Kontakte", + "groups": "Gruppen", + "@groups": {}, "newGroup": "Neue Gruppe", "@newGroup": {}, "selectMembers": "Mitglieder auswählen", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index cfb34bc..c8a3464 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -66,6 +66,7 @@ "@shareImagedEditorSavedImage": {}, "shareImageSearchAllContacts": "Search all contacts", "@shareImageSearchAllContacts": {}, + "startNewChatSearchHint": "Name, username or groupname", "shareImagedSelectAll": "Select all", "@shareImagedSelectAll": {}, "startNewChatTitle": "Select Contact", @@ -516,6 +517,8 @@ "durationShortMinute": "Min.", "durationShortHour": "Hrs.", "durationShortDays": "Days", + "contacts": "Contacts", + "groups": "Groups", "newGroup": "New group", "selectMembers": "Select members", "selectGroupName": "Select group name", diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index f1bf190..d434ff5 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -302,6 +302,12 @@ abstract class AppLocalizations { /// **'Search all contacts'** String get shareImageSearchAllContacts; + /// No description provided for @startNewChatSearchHint. + /// + /// In en, this message translates to: + /// **'Name, username or groupname'** + String get startNewChatSearchHint; + /// No description provided for @shareImagedSelectAll. /// /// In en, this message translates to: @@ -2198,6 +2204,18 @@ abstract class AppLocalizations { /// **'Days'** String get durationShortDays; + /// No description provided for @contacts. + /// + /// In en, this message translates to: + /// **'Contacts'** + String get contacts; + + /// No description provided for @groups. + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + /// No description provided for @newGroup. /// /// In en, this message translates to: diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index b346930..2cdc123 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -122,6 +122,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get shareImageSearchAllContacts => 'Alle Kontakte durchsuchen'; + @override + String get startNewChatSearchHint => 'Name, Benutzername oder Gruppenname'; + @override String get shareImagedSelectAll => 'Alle auswählen'; @@ -1166,6 +1169,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String get durationShortDays => 'Tagen'; + @override + String get contacts => 'Kontakte'; + + @override + String get groups => 'Gruppen'; + @override String get newGroup => 'Neue Gruppe'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 9dff907..facac30 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -121,6 +121,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get shareImageSearchAllContacts => 'Search all contacts'; + @override + String get startNewChatSearchHint => 'Name, username or groupname'; + @override String get shareImagedSelectAll => 'Select all'; @@ -1159,6 +1162,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get durationShortDays => 'Days'; + @override + String get contacts => 'Contacts'; + + @override + String get groups => 'Groups'; + @override String get newGroup => 'New group'; diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index 1db4ef1..8b5aea9 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -74,6 +74,7 @@ Future insertMediaFileInMessagesTable( message.groupId, const GroupsCompanion( archived: Value(false), + deletedContent: Value(false), ), ); } else { diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index 26db258..2b5acf8 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -191,7 +191,7 @@ class ContactsListView extends StatelessWidget { Tooltip( message: context.lang.searchUserNameArchiveUserTooltip, child: IconButton( - icon: const FaIcon(FontAwesomeIcons.boxArchive, size: 15), + icon: const FaIcon(Icons.archive_outlined, size: 15), onPressed: () async { const update = ContactsCompanion(requested: Value(false)); await twonlyDB.contactsDao.updateContact(contact.userId, update); diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index 1e47905..f6e9593 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -76,9 +76,9 @@ class _ChatMessagesViewState extends State { String currentInputText = ''; late StreamSubscription userSub; late StreamSubscription> messageSub; - late StreamSubscription>? groupActionsSub; - late StreamSubscription>? contactSub; - late StreamSubscription>>? + StreamSubscription>? groupActionsSub; + StreamSubscription>? contactSub; + StreamSubscription>>? lastOpenedMessageByContactSub; Map userIdToContact = {}; diff --git a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart index 7d7d3fb..03940ab 100644 --- a/lib/src/views/chats/chat_messages_components/chat_text_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_text_entry.dart @@ -47,16 +47,16 @@ class ChatTextEntry extends StatelessWidget { var displayTime = !combineTextMessageWithNext(message, nextMessage); var displayUserName = ''; - if (message.senderId != null && userIdToContact != null) { + if (message.senderId != null && + userIdToContact != null && + userIdToContact![message.senderId] != null) { if (prevMessage == null) { displayUserName = getContactDisplayName(userIdToContact![message.senderId]!); } else { if (!combineTextMessageWithNext(prevMessage!, message)) { - if (userIdToContact![message.senderId] != null) { - displayUserName = - getContactDisplayName(userIdToContact![message.senderId]!); - } + displayUserName = + getContactDisplayName(userIdToContact![message.senderId]!); } } } diff --git a/lib/src/views/chats/start_new_chat.view.dart b/lib/src/views/chats/start_new_chat.view.dart index 3f0f62b..a014939 100644 --- a/lib/src/views/chats/start_new_chat.view.dart +++ b/lib/src/views/chats/start_new_chat.view.dart @@ -10,6 +10,7 @@ import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/flame.dart'; +import 'package:twonly/src/views/components/group_context_menu.component.dart'; import 'package:twonly/src/views/components/user_context_menu.component.dart'; import 'package:twonly/src/views/groups/group_create_select_members.view.dart'; @@ -20,18 +21,20 @@ class StartNewChatView extends StatefulWidget { } class _StartNewChatView extends State { - List contacts = []; + List filteredContacts = []; + List filteredGroups = []; List allContacts = []; + List allNonDirectGroups = []; final TextEditingController searchUserName = TextEditingController(); late StreamSubscription> contactSub; + late StreamSubscription> allNonDirectGroupsSub; @override void initState() { super.initState(); - final stream = twonlyDB.contactsDao.watchAllAcceptedContacts(); - - contactSub = stream.listen((update) async { + contactSub = + twonlyDB.contactsDao.watchAllAcceptedContacts().listen((update) async { update.sort( (a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)), ); @@ -40,18 +43,28 @@ class _StartNewChatView extends State { }); await filterUsers(); }); + + allNonDirectGroupsSub = + twonlyDB.groupsDao.watchGroupsForStartNewChat().listen((update) async { + setState(() { + allNonDirectGroups = update; + }); + await filterUsers(); + }); } @override void dispose() { - unawaited(contactSub.cancel()); + allNonDirectGroupsSub.cancel(); + contactSub.cancel(); super.dispose(); } Future filterUsers() async { if (searchUserName.value.text.isEmpty) { setState(() { - contacts = allContacts; + filteredContacts = allContacts; + filteredGroups = []; }); return; } @@ -62,11 +75,54 @@ class _StartNewChatView extends State { .contains(searchUserName.value.text.toLowerCase()), ) .toList(); + final groupsFiltered = allNonDirectGroups + .where( + (g) => g.groupName + .toLowerCase() + .contains(searchUserName.value.text.toLowerCase()), + ) + .toList(); setState(() { - contacts = usersFiltered; + filteredContacts = usersFiltered; + filteredGroups = groupsFiltered; }); } + Future _onTapUser(Contact user) async { + var directChat = await twonlyDB.groupsDao.getDirectChat(user.userId); + if (directChat == null) { + await twonlyDB.groupsDao.createNewDirectChat( + user.userId, + GroupsCompanion( + groupName: Value( + getContactDisplayName(user), + ), + ), + ); + directChat = await twonlyDB.groupsDao.getDirectChat(user.userId); + } + if (!mounted) return; + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return ChatMessagesView(directChat!); + }, + ), + ); + } + + Future _onTapGroup(Group group) async { + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return ChatMessagesView(group); + }, + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -88,14 +144,137 @@ class _StartNewChatView extends State { controller: searchUserName, decoration: getInputDecoration( context, - context.lang.shareImageSearchAllContacts, + context.lang.startNewChatSearchHint, ), ), ), const SizedBox(height: 10), Expanded( - child: UserList( - contacts, + child: ListView.builder( + restorationId: 'new_message_users_list', + itemCount: + filteredContacts.length + 3 + filteredGroups.length, + itemBuilder: (BuildContext context, int i) { + if (searchUserName.text.isEmpty) { + if (i == 0) { + return ListTile( + title: Text(context.lang.newGroup), + leading: const CircleAvatar( + child: FaIcon( + FontAwesomeIcons.userGroup, + size: 13, + ), + ), + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const GroupCreateSelectMembersView(), + ), + ); + }, + ); + } + if (i == 1) { + return ListTile( + title: Text(context.lang.startNewChatNewContact), + leading: const CircleAvatar( + child: FaIcon( + FontAwesomeIcons.userPlus, + size: 13, + ), + ), + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AddNewUserView(), + ), + ); + }, + ); + } + if (i == 2) { + return const Divider(); + } + i = i - 3; + } else { + if (i == 0) { + return filteredContacts.isNotEmpty + ? ListTile( + title: Text( + context.lang.contacts, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ) + : Container(); + } else { + i -= 1; + } + } + + if (i < filteredContacts.length) { + return UserContextMenu( + key: Key(filteredContacts[i].userId.toString()), + contact: filteredContacts[i], + child: ListTile( + title: Row( + children: [ + Text(getContactDisplayName(filteredContacts[i])), + FlameCounterWidget( + contactId: filteredContacts[i].userId, + prefix: true, + ), + ], + ), + leading: AvatarIcon( + contactId: filteredContacts[i].userId, + fontSize: 13, + ), + onTap: () => _onTapUser(filteredContacts[i]), + ), + ); + } + i -= filteredContacts.length; + + if (i == 0) { + return filteredGroups.isNotEmpty + ? ListTile( + title: Text( + context.lang.groups, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ) + : Container(); + } + + i -= 1; + + if (i < filteredGroups.length) { + return GroupContextMenu( + key: Key(filteredGroups[i].groupId), + group: filteredGroups[i], + child: ListTile( + title: Text( + filteredGroups[i].groupName, + ), + leading: AvatarIcon( + group: filteredGroups[i], + fontSize: 13, + ), + onTap: () => _onTapGroup(filteredGroups[i]), + ), + ); + } + return Container(); + }, ), ), ], @@ -105,107 +284,3 @@ class _StartNewChatView extends State { ); } } - -class UserList extends StatelessWidget { - const UserList( - this.users, { - super.key, - }); - final List users; - - @override - Widget build(BuildContext context) { - return ListView.builder( - restorationId: 'new_message_users_list', - itemCount: users.length + 3, - itemBuilder: (BuildContext context, int i) { - if (i == 1) { - return ListTile( - title: Text(context.lang.startNewChatNewContact), - leading: const CircleAvatar( - child: FaIcon( - FontAwesomeIcons.userPlus, - size: 13, - ), - ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddNewUserView(), - ), - ); - }, - ); - } - if (i == 0) { - return ListTile( - title: Text(context.lang.newGroup), - leading: const CircleAvatar( - child: FaIcon( - FontAwesomeIcons.userGroup, - size: 13, - ), - ), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const GroupCreateSelectMembersView(), - ), - ); - }, - ); - } - if (i == 2) { - return const Divider(); - } - final user = users[i - 3]; - return UserContextMenu( - key: Key(user.userId.toString()), - contact: user, - child: ListTile( - title: Row( - children: [ - Text(getContactDisplayName(user)), - FlameCounterWidget( - contactId: user.userId, - prefix: true, - ), - ], - ), - leading: AvatarIcon( - contactId: user.userId, - fontSize: 13, - ), - onTap: () async { - var directChat = - await twonlyDB.groupsDao.getDirectChat(user.userId); - if (directChat == null) { - await twonlyDB.groupsDao.createNewDirectChat( - user.userId, - GroupsCompanion( - groupName: Value( - getContactDisplayName(user), - ), - ), - ); - directChat = - await twonlyDB.groupsDao.getDirectChat(user.userId); - } - if (!context.mounted) return; - await Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) { - return ChatMessagesView(directChat!); - }, - ), - ); - }, - ), - ); - }, - ); - } -} diff --git a/lib/src/views/components/group_context_menu.component.dart b/lib/src/views/components/group_context_menu.component.dart index b4d87f0..eb877dd 100644 --- a/lib/src/views/components/group_context_menu.component.dart +++ b/lib/src/views/components/group_context_menu.component.dart @@ -29,7 +29,7 @@ class GroupContextMenu extends StatelessWidget { await twonlyDB.groupsDao.updateGroup(group.groupId, update); } }, - icon: FontAwesomeIcons.boxArchive, + icon: Icons.archive_outlined, ), if (group.archived) ContextMenuItem( @@ -40,7 +40,7 @@ class GroupContextMenu extends StatelessWidget { await twonlyDB.groupsDao.updateGroup(group.groupId, update); } }, - icon: FontAwesomeIcons.boxOpen, + icon: Icons.unarchive_outlined, ), ContextMenuItem( title: context.lang.contextMenuOpenChat, @@ -71,6 +71,19 @@ class GroupContextMenu extends StatelessWidget { ? FontAwesomeIcons.thumbtackSlash : FontAwesomeIcons.thumbtack, ), + ContextMenuItem( + title: context.lang.delete, + icon: FontAwesomeIcons.trashCan, + onTap: () async { + await twonlyDB.messagesDao.deleteMessagesByGroupId(group.groupId); + await twonlyDB.groupsDao.updateGroup( + group.groupId, + const GroupsCompanion( + deletedContent: Value(true), + ), + ); + }, + ), ], child: child, ); diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index 28417bf..1720925 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -201,7 +201,7 @@ class _BackupViewState extends State { label: 'twonly Backup', ), BottomNavigationBarItem( - icon: const FaIcon(FontAwesomeIcons.boxArchive, size: 17), + icon: const FaIcon(Icons.archive_outlined, size: 17), label: context.lang.backupData, ), ],