224 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import "package:easy_localization/easy_localization.dart";
 | |
| import "package:flutter/material.dart";
 | |
| import "package:go_router/go_router.dart";
 | |
| import "package:flutter_hooks/flutter_hooks.dart";
 | |
| import "package:gap/gap.dart";
 | |
| import "package:hooks_riverpod/hooks_riverpod.dart";
 | |
| import "package:island/database/message.dart";
 | |
| import "package:island/models/chat.dart";
 | |
| import "package:island/pods/chat/messages_notifier.dart";
 | |
| import "package:island/pods/network.dart";
 | |
| import "package:island/services/responsive.dart";
 | |
| import "package:island/widgets/alert.dart";
 | |
| import "package:island/widgets/app_scaffold.dart";
 | |
| import "package:island/widgets/chat/message_item.dart";
 | |
| import "package:island/widgets/content/cloud_files.dart";
 | |
| import "package:island/widgets/response.dart";
 | |
| import "package:material_symbols_icons/material_symbols_icons.dart";
 | |
| import "package:styled_widget/styled_widget.dart";
 | |
| import "package:super_sliver_list/super_sliver_list.dart";
 | |
| import "package:material_symbols_icons/symbols.dart";
 | |
| 
 | |
| import "package:island/screens/chat/chat.dart";
 | |
| 
 | |
| class PublicRoomPreview extends HookConsumerWidget {
 | |
|   final String id;
 | |
|   final SnChatRoom room;
 | |
| 
 | |
|   const PublicRoomPreview({super.key, required this.id, required this.room});
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context, WidgetRef ref) {
 | |
|     final messages = ref.watch(messagesNotifierProvider(id));
 | |
|     final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
 | |
|     final scrollController = useScrollController();
 | |
| 
 | |
|     final listController = useMemoized(() => ListController(), []);
 | |
| 
 | |
|     var isLoading = false;
 | |
| 
 | |
|     // Add scroll listener for pagination
 | |
|     useEffect(() {
 | |
|       void onScroll() {
 | |
|         if (scrollController.position.pixels >=
 | |
|             scrollController.position.maxScrollExtent - 200) {
 | |
|           if (isLoading) return;
 | |
|           isLoading = true;
 | |
|           messagesNotifier.loadMore().then((_) => isLoading = false);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       scrollController.addListener(onScroll);
 | |
|       return () => scrollController.removeListener(onScroll);
 | |
|     }, [scrollController]);
 | |
| 
 | |
|     Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
 | |
|         SuperListView.builder(
 | |
|           listController: listController,
 | |
|           padding: EdgeInsets.symmetric(vertical: 16),
 | |
|           controller: scrollController,
 | |
|           reverse: true, // Show newest messages at the bottom
 | |
|           itemCount: messageList.length,
 | |
|           findChildIndexCallback: (key) {
 | |
|             final valueKey = key as ValueKey;
 | |
|             final messageId = valueKey.value as String;
 | |
|             return messageList.indexWhere((m) => m.id == messageId);
 | |
|           },
 | |
|           extentEstimation: (_, _) => 40,
 | |
|           itemBuilder: (context, index) {
 | |
|             final message = messageList[index];
 | |
|             final nextMessage =
 | |
|                 index < messageList.length - 1 ? messageList[index + 1] : null;
 | |
|             final isLastInGroup =
 | |
|                 nextMessage == null ||
 | |
|                 nextMessage.senderId != message.senderId ||
 | |
|                 nextMessage.createdAt
 | |
|                         .difference(message.createdAt)
 | |
|                         .inMinutes
 | |
|                         .abs() >
 | |
|                     3;
 | |
| 
 | |
|             return MessageItem(
 | |
|               message: message,
 | |
|               isCurrentUser: false, // User is not a member, so not current user
 | |
|               onAction: null, // No actions allowed in preview mode
 | |
|               onJump: (_) {}, // No jump functionality in preview
 | |
|               progress: null,
 | |
|               showAvatar: isLastInGroup,
 | |
|             );
 | |
|           },
 | |
|         );
 | |
| 
 | |
|     final compactHeader = isWideScreen(context);
 | |
| 
 | |
|     Widget comfortHeaderWidget() => Column(
 | |
|       spacing: 4,
 | |
|       mainAxisAlignment: MainAxisAlignment.center,
 | |
|       crossAxisAlignment: CrossAxisAlignment.center,
 | |
|       children: [
 | |
|         SizedBox(
 | |
|           height: 26,
 | |
|           width: 26,
 | |
|           child:
 | |
|               (room.type == 1 && room.picture?.id == null)
 | |
|                   ? SplitAvatarWidget(
 | |
|                     filesId:
 | |
|                         room.members!
 | |
|                             .map((e) => e.account.profile.picture?.id)
 | |
|                             .toList(),
 | |
|                   )
 | |
|                   : room.picture?.id != null
 | |
|                   ? ProfilePictureWidget(
 | |
|                     fileId: room.picture?.id,
 | |
|                     fallbackIcon: Symbols.chat,
 | |
|                   )
 | |
|                   : CircleAvatar(
 | |
|                     child: Text(
 | |
|                       room.name![0].toUpperCase(),
 | |
|                       style: const TextStyle(fontSize: 12),
 | |
|                     ),
 | |
|                   ),
 | |
|         ),
 | |
|         Text(
 | |
|           (room.type == 1 && room.name == null)
 | |
|               ? room.members!.map((e) => e.account.nick).join(', ')
 | |
|               : room.name!,
 | |
|         ).fontSize(15),
 | |
|       ],
 | |
|     );
 | |
| 
 | |
|     Widget compactHeaderWidget() => Row(
 | |
|       spacing: 8,
 | |
|       crossAxisAlignment: CrossAxisAlignment.center,
 | |
|       children: [
 | |
|         SizedBox(
 | |
|           height: 26,
 | |
|           width: 26,
 | |
|           child:
 | |
|               (room.type == 1 && room.picture?.id == null)
 | |
|                   ? SplitAvatarWidget(
 | |
|                     filesId:
 | |
|                         room.members!
 | |
|                             .map((e) => e.account.profile.picture?.id)
 | |
|                             .toList(),
 | |
|                   )
 | |
|                   : room.picture?.id != null
 | |
|                   ? ProfilePictureWidget(
 | |
|                     fileId: room.picture?.id,
 | |
|                     fallbackIcon: Symbols.chat,
 | |
|                   )
 | |
|                   : CircleAvatar(
 | |
|                     child: Text(
 | |
|                       room.name![0].toUpperCase(),
 | |
|                       style: const TextStyle(fontSize: 12),
 | |
|                     ),
 | |
|                   ),
 | |
|         ),
 | |
|         Text(
 | |
|           (room.type == 1 && room.name == null)
 | |
|               ? room.members!.map((e) => e.account.nick).join(', ')
 | |
|               : room.name!,
 | |
|         ).fontSize(19),
 | |
|       ],
 | |
|     );
 | |
| 
 | |
|     return AppScaffold(
 | |
|       appBar: AppBar(
 | |
|         leading: !compactHeader ? const Center(child: PageBackButton()) : null,
 | |
|         automaticallyImplyLeading: false,
 | |
|         toolbarHeight: compactHeader ? null : 64,
 | |
|         title: compactHeader ? compactHeaderWidget() : comfortHeaderWidget(),
 | |
|         actions: [
 | |
|           IconButton(
 | |
|             icon: const Icon(Icons.more_vert),
 | |
|             onPressed: () {
 | |
|               context.pushNamed('chatDetail', pathParameters: {'id': id});
 | |
|             },
 | |
|           ),
 | |
|           const Gap(8),
 | |
|         ],
 | |
|       ),
 | |
|       body: Column(
 | |
|         crossAxisAlignment: CrossAxisAlignment.stretch,
 | |
|         children: [
 | |
|           Expanded(
 | |
|             child: messages.when(
 | |
|               data:
 | |
|                   (messageList) =>
 | |
|                       messageList.isEmpty
 | |
|                           ? Center(child: Text('No messages yet'.tr()))
 | |
|                           : chatMessageListWidget(messageList),
 | |
|               loading: () => const Center(child: CircularProgressIndicator()),
 | |
|               error:
 | |
|                   (error, _) => ResponseErrorWidget(
 | |
|                     error: error,
 | |
|                     onRetry: () => messagesNotifier.loadInitial(),
 | |
|                   ),
 | |
|             ),
 | |
|           ),
 | |
|           // Join button at the bottom for public rooms
 | |
|           Container(
 | |
|             padding: const EdgeInsets.all(16),
 | |
|             child: FilledButton.tonalIcon(
 | |
|               onPressed: () async {
 | |
|                 try {
 | |
|                   showLoadingModal(context);
 | |
|                   final apiClient = ref.read(apiClientProvider);
 | |
|                   await apiClient.post('/sphere/chat/${room.id}/members/me');
 | |
|                   ref.invalidate(chatroomIdentityProvider(id));
 | |
|                 } catch (err) {
 | |
|                   showErrorAlert(err);
 | |
|                 } finally {
 | |
|                   if (context.mounted) hideLoadingModal(context);
 | |
|                 }
 | |
|               },
 | |
|               label: Text('chatJoin').tr(),
 | |
|               icon: const Icon(Icons.add),
 | |
|             ),
 | |
|           ),
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |