-
Notifications
You must be signed in to change notification settings - Fork 12
feat: group admin edit, media tab, confirm modals #2490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/encrypted-group
Are you sure you want to change the base?
Conversation
lib/app/features/auth/views/components/user_data_inputs/general_user_data_input.dart
Outdated
Show resolved
Hide resolved
lib/app/features/auth/views/components/user_data_inputs/general_user_data_input.dart
Outdated
Show resolved
Hide resolved
lib/app/features/chat/e2ee/providers/group/group_media_provider.r.dart
Outdated
Show resolved
Hide resolved
lib/app/features/chat/e2ee/providers/group/update_group_metadata_service.r.dart
Show resolved
Hide resolved
...tures/chat/e2ee/views/pages/group_admin_page/components/confirm_admin_role_assign_modal.dart
Outdated
Show resolved
Hide resolved
lib/app/features/chat/e2ee/views/pages/group_admin_page/components/group_admins_modal.dart
Show resolved
Hide resolved
| File? groupImageFile; | ||
| if (groupImageFileResult.hasData) { | ||
| final file = groupImageFileResult.data; | ||
| if (file != null && file.existsSync() && file.lengthSync() > 0) { | ||
| groupImageFile = file; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for each rebuild, it ll check length and exists sync, should we keep in state and perform only when related file is changed?
lib/app/features/chat/e2ee/views/pages/group_admin_page/components/group_media_tab.dart
Outdated
Show resolved
Hide resolved
lib/app/features/chat/e2ee/views/pages/group_admin_page/components/group_media_tab.dart
Outdated
Show resolved
Hide resolved
...p/features/chat/e2ee/views/pages/group_admin_page/components/select_administrator_modal.dart
Outdated
Show resolved
Hide resolved
| // Extract links from content | ||
| final content = entity.data.content; | ||
| if (content.isNotEmpty) { | ||
| final textParser = TextParser(matchers: {const UrlMatcher()}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can define it out of the for loop to do not create for each item. wdyt?
| for (final messages in messagesByDate.values) { | ||
| for (final eventMessage in messages) { | ||
| try { | ||
| final entity = EncryptedGroupMessageEntity.fromEventMessage(eventMessage); | ||
| final eventReference = entity.toEventReference(); | ||
| final publishedAt = entity.data.publishedAt.value; | ||
|
|
||
| // Extract links from content | ||
| final content = entity.data.content; | ||
| if (content.isNotEmpty) { | ||
| final textParser = TextParser(matchers: {const UrlMatcher()}); | ||
| final parsed = textParser.parse(content, onlyMatches: true); | ||
| final links = parsed | ||
| .where((match) => match.matcher is UrlMatcher) | ||
| .map((match) => match.text) | ||
| .toSet(); // Use Set to avoid duplicates | ||
|
|
||
| for (final link in links) { | ||
| // Exclude media URLs (they're already in media attachments) | ||
| final isMediaUrl = entity.data.media.values.any( | ||
| (media) => media.url == link || media.thumb == link || media.image == link, | ||
| ); | ||
| if (!isMediaUrl) { | ||
| allLinks.add( | ||
| GroupLinkItem( | ||
| url: link, | ||
| eventReference: eventReference, | ||
| publishedAt: publishedAt, | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s assume a group conversation has a few thousand messages. I believe it’s quite feasible to reach that count for groups. Does this cause UI lag? We could load it in pages to enhance performance. What are your thoughts on this? If it does cause lag, I’m also open to handling follow-up tasks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same concerns for files, media and voice tabs
| itemBuilder: (context, int index) { | ||
| final fileItem = files[index]; | ||
| return _GroupFileCell( | ||
| fileName: fileItem.media.alt ?? 'File', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all files should have alt prop which indicates the file name - if we need it hence of null property definition, we can define this magic string into the l18n. wdyt?
| ) | ||
| : null; | ||
|
|
||
| final fileSizeStr = localMediaPath != null ? formattedFileSize(localMediaPath) ?? '0kb' : '0kb'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we sure about showing 0kb while loading the media? we can align it with designer wdyt?
| String _formatDateAndTime(int timestamp) { | ||
| final date = timestamp.toDateTime; | ||
| final dateStr = DateFormat('d MMM yyyy').format(date); | ||
| final timeStr = DateFormat('HH:mm').format(date); | ||
| return '$dateStr • $timeStr'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we handle in one DateFormat('d MMM yyyy • HH:mm');
also we can move it to the date.dart util class. wdyt?
| part 'group_links_provider.r.g.dart'; | ||
|
|
||
| @riverpod | ||
| Future<List<GroupLinkItem>> groupLinks( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider creating GroupLinksService class for the logic and use Riverpod only to create it's instance, like this:
@riverpod
GroupLinksService groupLinksService(GroupLinksServiceRef ref) {
final dao = ref.watch(conversationMessageDaoProvider);
final parser = TextParser(matchers: {const UrlMatcher()});
return GroupLinksService(
conversationMessageDao: dao,
textParser: parser,
);
}
| part 'group_media_provider.r.g.dart'; | ||
|
|
||
| @riverpod | ||
| Future<List<GroupMediaItem>> groupMedia( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider extracting all parsing, flattening, and sorting work into a dedicated GroupMediaService class.
Use Riverpod only to construct the service and call its methods:
@riverpod
GroupMediaService groupMediaService(GroupMediaServiceRef ref) {
final dao = ref.watch(conversationMessageDaoProvider);
return GroupMediaService(dao);
}
Then groupMedia, groupMediaItems, groupVoiceItems, and groupFilesItems should only delegate to this service instead of performing business logic inside the providers.
| final userPreviewData = ref.watch(userPreviewDataProvider(participantMasterkey)).valueOrNull; | ||
|
|
||
| if (userPreviewData == null) { | ||
| return const SizedBox.shrink(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a loader here?
| Widget build(BuildContext context, WidgetRef ref) { | ||
| final groupMetadata = ref.watch(encryptedGroupMetadataProvider(conversationId)).valueOrNull; | ||
|
|
||
| final adminsAndOwners = groupMetadata?.members |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a business logic, consider extracting it from the widget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating sortedAdmins too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also calculating isMaxAdminsReached.
| .toList(); | ||
|
|
||
| if (adminsAndOwners == null || adminsAndOwners.isEmpty) { | ||
| return const SizedBox.shrink(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should a skeleton/loading be implemented here?
| ), | ||
| ).data | ||
| : null; | ||
| final mediaFuture = useMemoized<Future<File?>>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic belongs in a provider, not in a widget hook.
|
|
||
| @override | ||
| Widget build(BuildContext context, WidgetRef ref) { | ||
| final eventMessageFuture = useFuture( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic belongs in a provider, not in a widget hook.
| ); | ||
| } | ||
|
|
||
| final fileFuture = useFuture<String?>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic belongs in a provider, not in a widget hook.
| .select(userPreviewDisplayNameSelector), | ||
| ); | ||
|
|
||
| final eventMessageFuture = useFuture( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic belongs in a provider, not in a widget hook.
| ), | ||
| ); | ||
|
|
||
| final entity = useMemoized( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic belongs in a provider, not in a widget hook.
| final audioPlaybackController = useAudioWavePlaybackController() | ||
| ..setFinishMode(finishMode: FinishMode.pause); | ||
|
|
||
| final eventMessageId = useMemoized( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic belongs in a provider, not in a widget hook.
| label: Text(context.i18n.button_delete), | ||
| onPressed: () { | ||
| context.pop(); | ||
| //todo add delete conversation logic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use standard formatting for TODOs, so that they would be shown in the IDE, e.g. // TODO(ice-erebus): add recent coins
internal review
group edit page
group admin media tab
confirmation modals