diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 33019c8d..2ed141e2 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2738,6 +2738,18 @@ abstract class AppLocalizations { /// **'{username} has scanned your QR code and is now verified.'** String secretQrTokenVerifiedSnackbar(Object username); + /// No description provided for @mutualGroupsTitle. + /// + /// In en, this message translates to: + /// **'{count, plural, =1{1 mutual group} other{{count} mutual groups}}'** + String mutualGroupsTitle(num count); + + /// No description provided for @mutualGroupsSentMessages. + /// + /// In en, this message translates to: + /// **'{count, plural, =1{1 message sent} other{{count} messages sent}}'** + String mutualGroupsSentMessages(num count); + /// No description provided for @chatEntryFlameRestored. /// /// 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 084d44ff..83629f9a 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1528,6 +1528,28 @@ class AppLocalizationsDe extends AppLocalizations { return '$username hat deinen QR-Code gescannt und ist nun verifiziert.'; } + @override + String mutualGroupsTitle(num count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count gemeinsame Gruppen', + one: '1 gemeinsame Gruppe', + ); + return '$_temp0'; + } + + @override + String mutualGroupsSentMessages(num count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count Nachrichten gesendet', + one: '1 Nachricht gesendet', + ); + return '$_temp0'; + } + @override String chatEntryFlameRestored(Object count) { return '$count Flammen wiederhergestellt'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index a5cc0f47..aafbf9dc 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1513,6 +1513,28 @@ class AppLocalizationsEn extends AppLocalizations { return '$username has scanned your QR code and is now verified.'; } + @override + String mutualGroupsTitle(num count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count mutual groups', + one: '1 mutual group', + ); + return '$_temp0'; + } + + @override + String mutualGroupsSentMessages(num count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages sent', + one: '1 message sent', + ); + return '$_temp0'; + } + @override String chatEntryFlameRestored(Object count) { return '$count flames restored'; diff --git a/lib/src/localization/translations b/lib/src/localization/translations index 0675e745..3a9c589d 160000 --- a/lib/src/localization/translations +++ b/lib/src/localization/translations @@ -1 +1 @@ -Subproject commit 0675e74501d6610a84273232517652db25965e3f +Subproject commit 3a9c589de2d2abc0042004dbfe943fa4fb2c92c7 diff --git a/lib/src/visual/views/contact/contact.view.dart b/lib/src/visual/views/contact/contact.view.dart index b7d76ba0..aa0343c2 100644 --- a/lib/src/visual/views/contact/contact.view.dart +++ b/lib/src/visual/views/contact/contact.view.dart @@ -8,7 +8,6 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/user_discovery.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; @@ -17,10 +16,11 @@ import 'package:twonly/src/visual/components/select_chat_deletion_time.comp.dart import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/components/verification_badge.comp.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart'; +import 'package:twonly/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.comp.dart'; import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart'; +import 'package:twonly/src/visual/views/contact/contact_components/user_discovery_contact_settings.comp.dart'; import 'package:twonly/src/visual/views/contact/contact_components/verification_expansion_tile.comp.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart'; -import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart'; class ContactView extends StatefulWidget { const ContactView(this.userId, {super.key}); @@ -93,14 +93,6 @@ class _ContactViewState extends State { ); if (remove) { await twonlyDB.contactsDao.deleteContactByUserId(contact.userId); - // await twonlyDB.contactsDao.updateContact( - // contact.userId, - // const ContactsCompanion( - // accepted: Value(false), - // requested: Value(false), - // deletedByUser: Value(true), - // ), - // ); if (mounted) { Navigator.popUntil(context, (route) => route.isFirst); } @@ -240,62 +232,13 @@ class _ContactViewState extends State { VerificationExpansionTileComp( contact: contact, ), - if (userService.currentUser.isUserDiscoveryEnabled) - if (userService.currentUser.userDiscoveryRequiresManualApproval && - contact.userDiscoveryManualApproved != true) - BetterListTile( - icon: FontAwesomeIcons.usersViewfinder, - text: context.lang.userDiscoverySettingsTitle, - subtitle: Text( - context.lang.contactUserDiscoveryManualApprovalPending, - style: const TextStyle(fontSize: 10), - ), - trailing: TextButton( - onPressed: () async { - await twonlyDB.contactsDao.updateContact( - contact.userId, - const ContactsCompanion( - userDiscoveryManualApproved: Value(true), - ), - ); - }, - child: Text( - context.lang.contactUserDiscoveryManualApprovalApprove, - ), - ), - ) - else - BetterListTile( - icon: FontAwesomeIcons.usersViewfinder, - text: context.lang.userDiscoverySettingsTitle, - onTap: () => context.navPush(const UserDiscoverySettingsView()), - subtitle: - !contact.userDiscoveryExcluded && - contact.mediaSendCounter < - userService.currentUser.requiredSendImages - ? Text( - context.lang.contactUserDiscoveryImagesLeft( - userService.currentUser.requiredSendImages - - contact.mediaSendCounter, - getContactDisplayName(contact), - ), - style: const TextStyle(fontSize: 9), - ) - : null, - trailing: Transform.scale( - scale: 0.8, - child: Switch( - value: !contact.userDiscoveryExcluded, - onChanged: (a) async { - await UserDiscoveryService.changeExclusionForContact( - contact.userId, - !a, - ); - }, - ), - ), - ), - + MutualGroupsExpansionTileComp( + contact: contact, + ), + UserDiscoveryContactSettingsComp( + contact: contact, + ), + const Divider(), BetterListTile( icon: FontAwesomeIcons.flag, text: context.lang.reportUser, diff --git a/lib/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.comp.dart b/lib/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.comp.dart new file mode 100644 index 00000000..8f56eea9 --- /dev/null +++ b/lib/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.comp.dart @@ -0,0 +1,112 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; + +class MutualGroupsExpansionTileComp extends StatefulWidget { + const MutualGroupsExpansionTileComp({ + required this.contact, + super.key, + }); + + final Contact contact; + + @override + State createState() => + _MutualGroupsExpansionTileCompState(); +} + +class _MutualGroupsExpansionTileCompState + extends State { + List _groups = []; + late StreamSubscription> _streamGroups; + bool? _hasInitializedExpanded; + + @override + void initState() { + super.initState(); + _streamGroups = twonlyDB.groupsDao + .watchNonDirectGroupsForMember(widget.contact.userId) + .listen((groupsList) { + if (!mounted) return; + setState(() { + _groups = groupsList; + _groups.sort((a, b) { + return b.totalMediaCounter.compareTo(a.totalMediaCounter); + }); + _hasInitializedExpanded ??= true; + }); + }); + } + + @override + void dispose() { + _streamGroups.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_hasInitializedExpanded == null || _groups.isEmpty) { + return const SizedBox.shrink(); + } + + return ExpansionTile( + key: PageStorageKey('mutual_groups_${widget.contact.userId}'), + shape: const RoundedRectangleBorder(), + backgroundColor: context.color.surfaceContainer, + collapsedShape: const RoundedRectangleBorder(), + initiallyExpanded: _groups.length < 5, + onExpansionChanged: (expanded) { + setState(() {}); + }, + leading: Padding( + padding: const EdgeInsets.only(left: 14, right: 14), + child: SizedBox( + width: 20, + height: 20, + child: Icon( + FontAwesomeIcons.userGroup, + size: 16, + color: context.color.onSurfaceVariant, + ), + ), + ), + title: Text( + context.lang.mutualGroupsTitle(_groups.length), + style: TextStyle( + color: context.color.onSurface, + ), + ), + children: _groups.map((group) { + return ListTile( + dense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + leading: AvatarIcon( + group: group, + fontSize: 14, + ), + title: Text( + group.groupName, + style: TextStyle( + color: context.color.onSurface, + fontWeight: FontWeight.w400, + ), + ), + onTap: () { + context.push(Routes.chatsMessages(group.groupId)); + }, + ); + }).toList(), + ); + } +} diff --git a/lib/src/visual/views/contact/contact_components/user_discovery_contact_settings.comp.dart b/lib/src/visual/views/contact/contact_components/user_discovery_contact_settings.comp.dart new file mode 100644 index 00000000..c60d19c7 --- /dev/null +++ b/lib/src/visual/views/contact/contact_components/user_discovery_contact_settings.comp.dart @@ -0,0 +1,81 @@ +import 'package:drift/drift.dart' hide Column; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/user_discovery.service.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/elements/better_list_title.element.dart'; +import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart'; + +class UserDiscoveryContactSettingsComp extends StatelessWidget { + const UserDiscoveryContactSettingsComp({ + required this.contact, + super.key, + }); + + final Contact contact; + + @override + Widget build(BuildContext context) { + if (!userService.currentUser.isUserDiscoveryEnabled) { + return const SizedBox.shrink(); + } + + if (userService.currentUser.userDiscoveryRequiresManualApproval && + contact.userDiscoveryManualApproved != true) { + return BetterListTile( + icon: FontAwesomeIcons.usersViewfinder, + text: context.lang.userDiscoverySettingsTitle, + subtitle: Text( + context.lang.contactUserDiscoveryManualApprovalPending, + style: const TextStyle(fontSize: 10), + ), + trailing: TextButton( + onPressed: () async { + await twonlyDB.contactsDao.updateContact( + contact.userId, + const ContactsCompanion( + userDiscoveryManualApproved: Value(true), + ), + ); + }, + child: Text( + context.lang.contactUserDiscoveryManualApprovalApprove, + ), + ), + ); + } + + return BetterListTile( + icon: FontAwesomeIcons.usersViewfinder, + text: context.lang.userDiscoverySettingsTitle, + onTap: () => context.navPush(const UserDiscoverySettingsView()), + subtitle: !contact.userDiscoveryExcluded && + contact.mediaSendCounter < + userService.currentUser.requiredSendImages + ? Text( + context.lang.contactUserDiscoveryImagesLeft( + userService.currentUser.requiredSendImages - + contact.mediaSendCounter, + getContactDisplayName(contact), + ), + style: const TextStyle(fontSize: 9), + ) + : null, + trailing: Transform.scale( + scale: 0.8, + child: Switch( + value: !contact.userDiscoveryExcluded, + onChanged: (a) async { + await UserDiscoveryService.changeExclusionForContact( + contact.userId, + !a, + ); + }, + ), + ), + ); + } +}