import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:surface/providers/post.dart'; import 'package:surface/types/post.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/post/post_item.dart'; import 'package:surface/widgets/post/post_tags_field.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; class PostSearchScreen extends StatefulWidget { const PostSearchScreen({super.key}); @override State createState() => _PostSearchScreenState(); } class _PostSearchScreenState extends State { bool _isBusy = false; List _searchTags = List.empty(growable: true); final List _posts = List.empty(growable: true); int? _postCount; String _searchTerm = ''; Duration? _lastTook; Future _fetchPosts() async { if (_searchTerm.isEmpty && _searchTags.isEmpty) return; if (_postCount != null && _posts.length >= _postCount!) return; setState(() => _isBusy = true); final stopwatch = Stopwatch()..start(); try { final pt = context.read(); final result = await pt.searchPosts( _searchTerm, take: 10, offset: _posts.length, tags: _searchTags, ); final List out = result.$1; _postCount = result.$2; _posts.addAll(out); } catch (err) { if (!mounted) return; context.showErrorDialog(err); return; } finally { stopwatch.stop(); _lastTook = stopwatch.elapsed; } if (mounted) setState(() => _isBusy = false); } void _showAdvancedSearchTune() { showModalBottomSheet( context: context, builder: (context) => Column( children: [ PostTagsField( labelText: 'fieldPostTags'.tr(), initialTags: _searchTags, onUpdate: (value) { setState(() => _searchTags = value); }, ), ], ).padding(horizontal: 24, vertical: 16), ); } @override Widget build(BuildContext context) { const labelShadows = [ Shadow( offset: Offset(1, 1), blurRadius: 8.0, color: Color.fromARGB(255, 0, 0, 0), ), ]; return Scaffold( appBar: AppBar( title: Text('screenPostSearch').tr(), actions: [ IconButton( icon: const Icon(Symbols.tune), onPressed: _showAdvancedSearchTune, ), const Gap(8), ], ), body: Stack( children: [ InfiniteList( padding: const EdgeInsets.only(top: 100), itemCount: _posts.length, isLoading: _isBusy, hasReachedMax: _postCount != null && _posts.length >= _postCount!, onFetchData: () { _fetchPosts(); }, itemBuilder: (context, idx) { return GestureDetector( child: PostItem( data: _posts[idx], maxWidth: 640, onChanged: (data) { setState(() => _posts[idx] = data); }, onDeleted: () { _posts.clear(); _fetchPosts(); }, ), onTap: () { GoRouter.of(context).pushNamed( 'postDetail', pathParameters: {'slug': _posts[idx].id.toString()}, extra: _posts[idx], ); }, ); }, separatorBuilder: (context, index) => const Divider(height: 1), ), Positioned( top: 16, left: 16, right: 16, child: Column( children: [ SearchBar( elevation: const WidgetStatePropertyAll(1), leading: const Icon(Symbols.search), padding: const WidgetStatePropertyAll( EdgeInsets.symmetric(horizontal: 24), ), onChanged: (value) { _searchTerm = value; }, onSubmitted: (value) { setState(() => _posts.clear()); _searchTerm = value; _fetchPosts(); }, ), if (_lastTook != null) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Symbols.summarize, color: Colors.white, shadows: labelShadows, size: 16, ), const Gap(4), Text( 'postSearchResult'.plural(_postCount ?? 0), style: TextStyle( color: Colors.white, shadows: labelShadows, fontSize: 13, ), ), const Gap(8), Icon( Symbols.pace, color: Colors.white, shadows: labelShadows, size: 16, ), const Gap(4), Text( 'postSearchTook'.tr(args: [ '${(_lastTook!.inMilliseconds / 1000).toStringAsFixed(3)}s', ]), style: TextStyle( color: Colors.white, shadows: labelShadows, fontSize: 13, ), ), ], ).padding(vertical: 8), ], ), ), ], ), ); } }