shwo mutual groups
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2026-05-19 15:41:37 +02:00
parent 304190387d
commit 6f8f1efe81
7 changed files with 259 additions and 67 deletions

View file

@ -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:

View file

@ -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';

View file

@ -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';

@ -1 +1 @@
Subproject commit 0675e74501d6610a84273232517652db25965e3f
Subproject commit 3a9c589de2d2abc0042004dbfe943fa4fb2c92c7

View file

@ -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<ContactView> {
);
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<ContactView> {
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),
MutualGroupsExpansionTileComp(
contact: contact,
),
trailing: TextButton(
onPressed: () async {
await twonlyDB.contactsDao.updateContact(
contact.userId,
const ContactsCompanion(
userDiscoveryManualApproved: Value(true),
UserDiscoveryContactSettingsComp(
contact: contact,
),
);
},
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,
);
},
),
),
),
const Divider(),
BetterListTile(
icon: FontAwesomeIcons.flag,
text: context.lang.reportUser,

View file

@ -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<MutualGroupsExpansionTileComp> createState() =>
_MutualGroupsExpansionTileCompState();
}
class _MutualGroupsExpansionTileCompState
extends State<MutualGroupsExpansionTileComp> {
List<Group> _groups = [];
late StreamSubscription<List<Group>> _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<String>('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(),
);
}
}

View file

@ -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,
);
},
),
),
);
}
}