import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; class ChatSearchFilesTab extends StatelessWidget { final Room room; final Stream<(List, String?)>? searchStream; final void Function({ String? prevBatch, List? previousSearchResult, }) startSearch; const ChatSearchFilesTab({ required this.room, required this.startSearch, required this.searchStream, super.key, }); @override Widget build(BuildContext context) { return StreamBuilder( stream: searchStream, builder: (context, snapshot) { 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.file_present_outlined, size: 64), const SizedBox(height: 8), Text(L10n.of(context)!.nothingFound), ], ); } return SelectionArea( child: ListView.builder( padding: const EdgeInsets.all(8.0), itemCount: events.length + 1, itemBuilder: (context, i) { if (i == events.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.of(context).colorScheme.secondaryContainer, foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer, ), onPressed: () => startSearch( prevBatch: nextBatch, previousSearchResult: events, ), icon: const Icon( Icons.arrow_downward_outlined, ), label: Text(L10n.of(context)!.searchMore), ), ), ); } final event = events[i]; final filename = event.content.tryGet('filename') ?? event.content.tryGet('body') ?? L10n.of(context)!.unknownEvent('File'); final filetype = (filename.contains('.') ? filename.split('.').last.toUpperCase() : event.content .tryGetMap('info') ?.tryGet('mimetype') ?.toUpperCase() ?? 'UNKNOWN'); final sizeString = event.sizeString; final prevEvent = i > 0 ? events[i - 1] : null; final sameEnvironment = prevEvent == null ? false : prevEvent.originServerTs .sameEnvironment(event.originServerTs); return Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ if (!sameEnvironment) ...[ Row( children: [ Expanded( child: Container( height: 1, color: Theme.of(context).dividerColor, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( event.originServerTs.localizedTime(context), style: Theme.of(context).textTheme.labelSmall, textAlign: TextAlign.center, ), ), Expanded( child: Container( height: 1, color: Theme.of(context).dividerColor, ), ), ], ), const SizedBox(height: 4), ], Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius), color: Theme.of(context).colorScheme.onInverseSurface, clipBehavior: Clip.hardEdge, child: ListTile( leading: const Icon(Icons.file_present_outlined), title: Text( filename, maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text('$sizeString | $filetype'), onTap: () => event.saveFile(context), ), ), ], ), ); }, ), ); }, ); } }