196 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
| import 'package:flutter/material.dart';
 | |
| 
 | |
| import 'package:fluffychat/generated/l10n/l10n.dart';
 | |
| import 'package:intl/intl.dart';
 | |
| import 'package:matrix/matrix.dart';
 | |
| 
 | |
| import 'package:fluffychat/config/app_config.dart';
 | |
| import 'package:fluffychat/pages/chat/events/video_player.dart';
 | |
| import 'package:fluffychat/pages/image_viewer/image_viewer.dart';
 | |
| import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
 | |
| import 'package:fluffychat/widgets/mxc_image.dart';
 | |
| 
 | |
| class ChatSearchImagesTab extends StatelessWidget {
 | |
|   final Room room;
 | |
|   final Stream<(List<Event>, String?)>? searchStream;
 | |
|   final void Function({
 | |
|     String? prevBatch,
 | |
|     List<Event>? previousSearchResult,
 | |
|   }) startSearch;
 | |
| 
 | |
|   const ChatSearchImagesTab({
 | |
|     required this.room,
 | |
|     required this.startSearch,
 | |
|     required this.searchStream,
 | |
|     super.key,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     final theme = Theme.of(context);
 | |
|     final borderRadius = BorderRadius.circular(AppConfig.borderRadius / 2);
 | |
|     return StreamBuilder(
 | |
|       stream: searchStream,
 | |
|       builder: (context, snapshot) {
 | |
|         final theme = Theme.of(context);
 | |
|         final events = snapshot.data?.$1;
 | |
|         if (searchStream == null || events == null) {
 | |
|           return Column(
 | |
|             mainAxisAlignment: MainAxisAlignment.center,
 | |
|             children: [
 | |
|               const CircularProgressIndicator.adaptive(strokeWidth: 2),
 | |
|               const SizedBox(height: 8),
 | |
|               Text(
 | |
|                 L10n.of(context).searchIn(
 | |
|                   room.getLocalizedDisplayname(
 | |
|                     MatrixLocals(L10n.of(context)),
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
|             ],
 | |
|           );
 | |
|         }
 | |
|         if (events.isEmpty) {
 | |
|           return Column(
 | |
|             mainAxisAlignment: MainAxisAlignment.center,
 | |
|             children: [
 | |
|               const Icon(Icons.photo_outlined, size: 64),
 | |
|               const SizedBox(height: 8),
 | |
|               Text(L10n.of(context).nothingFound),
 | |
|             ],
 | |
|           );
 | |
|         }
 | |
|         final eventsByMonth = <DateTime, List<Event>>{};
 | |
|         for (final event in events) {
 | |
|           final month = DateTime(
 | |
|             event.originServerTs.year,
 | |
|             event.originServerTs.month,
 | |
|           );
 | |
|           eventsByMonth[month] ??= [];
 | |
|           eventsByMonth[month]!.add(event);
 | |
|         }
 | |
|         final eventsByMonthList = eventsByMonth.entries.toList();
 | |
| 
 | |
|         const padding = 8.0;
 | |
| 
 | |
|         return ListView.builder(
 | |
|           itemCount: eventsByMonth.length + 1,
 | |
|           itemBuilder: (context, i) {
 | |
|             if (i == eventsByMonth.length) {
 | |
|               if (snapshot.connectionState != ConnectionState.done) {
 | |
|                 return const Padding(
 | |
|                   padding: EdgeInsets.all(16.0),
 | |
|                   child: Center(
 | |
|                     child: CircularProgressIndicator.adaptive(
 | |
|                       strokeWidth: 2,
 | |
|                     ),
 | |
|                   ),
 | |
|                 );
 | |
|               }
 | |
|               final nextBatch = snapshot.data?.$2;
 | |
|               if (nextBatch == null) {
 | |
|                 return const SizedBox.shrink();
 | |
|               }
 | |
|               return Center(
 | |
|                 child: Padding(
 | |
|                   padding: const EdgeInsets.all(16.0),
 | |
|                   child: TextButton.icon(
 | |
|                     style: TextButton.styleFrom(
 | |
|                       backgroundColor: theme.colorScheme.secondaryContainer,
 | |
|                       foregroundColor: theme.colorScheme.onSecondaryContainer,
 | |
|                     ),
 | |
|                     onPressed: () => startSearch(
 | |
|                       prevBatch: nextBatch,
 | |
|                       previousSearchResult: events,
 | |
|                     ),
 | |
|                     icon: const Icon(
 | |
|                       Icons.arrow_downward_outlined,
 | |
|                     ),
 | |
|                     label: Text(L10n.of(context).searchMore),
 | |
|                   ),
 | |
|                 ),
 | |
|               );
 | |
|             }
 | |
| 
 | |
|             final monthEvents = eventsByMonthList[i].value;
 | |
|             return Column(
 | |
|               mainAxisSize: MainAxisSize.min,
 | |
|               children: [
 | |
|                 const SizedBox(height: 4),
 | |
|                 Row(
 | |
|                   children: [
 | |
|                     Expanded(
 | |
|                       child: Container(
 | |
|                         height: 1,
 | |
|                         color: theme.dividerColor,
 | |
|                       ),
 | |
|                     ),
 | |
|                     Padding(
 | |
|                       padding: const EdgeInsets.all(8.0),
 | |
|                       child: Text(
 | |
|                         DateFormat.yMMMM(
 | |
|                           Localizations.localeOf(context).languageCode,
 | |
|                         ).format(eventsByMonthList[i].key),
 | |
|                         style: theme.textTheme.labelSmall,
 | |
|                         textAlign: TextAlign.center,
 | |
|                       ),
 | |
|                     ),
 | |
|                     Expanded(
 | |
|                       child: Container(
 | |
|                         height: 1,
 | |
|                         color: theme.dividerColor,
 | |
|                       ),
 | |
|                     ),
 | |
|                   ],
 | |
|                 ),
 | |
|                 GridView.count(
 | |
|                   physics: const NeverScrollableScrollPhysics(),
 | |
|                   shrinkWrap: true,
 | |
|                   mainAxisSpacing: padding,
 | |
|                   crossAxisSpacing: padding,
 | |
|                   clipBehavior: Clip.hardEdge,
 | |
|                   padding: const EdgeInsets.all(padding),
 | |
|                   crossAxisCount: 3,
 | |
|                   children: monthEvents.map(
 | |
|                     (event) {
 | |
|                       if (event.messageType == MessageTypes.Video) {
 | |
|                         return Material(
 | |
|                           clipBehavior: Clip.hardEdge,
 | |
|                           borderRadius: borderRadius,
 | |
|                           child: EventVideoPlayer(event, theme.colorScheme.onSurface, theme.colorScheme.primary),
 | |
|                         );
 | |
|                       }
 | |
|                       return InkWell(
 | |
|                         onTap: () => showDialog(
 | |
|                           context: context,
 | |
|                           builder: (_) => ImageViewer(
 | |
|                             event,
 | |
|                             outerContext: context,
 | |
|                           ),
 | |
|                         ),
 | |
|                         borderRadius: borderRadius,
 | |
|                         child: Material(
 | |
|                           clipBehavior: Clip.hardEdge,
 | |
|                           borderRadius: borderRadius,
 | |
|                           child: MxcImage(
 | |
|                             event: event,
 | |
|                             width: 128,
 | |
|                             height: 128,
 | |
|                             fit: BoxFit.cover,
 | |
|                             animated: true,
 | |
|                             isThumbnail: true,
 | |
|                           ),
 | |
|                         ),
 | |
|                       );
 | |
|                     },
 | |
|                   ).toList(),
 | |
|                 ),
 | |
|               ],
 | |
|             );
 | |
|           },
 | |
|         );
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| }
 |