import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/scaffold.dart'; import 'package:PiliPlus/common/widgets/sliver_wrap.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/search/search_rcmd/data.dart'; import 'package:PiliPlus/pages/search/controller.dart'; import 'package:PiliPlus/pages/search/widgets/hot_keyword.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart'; import 'package:PiliPlus/utils/em.dart' show Em; import 'package:PiliPlus/utils/extension/size_ext.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart' hide LayoutBuilder; import 'package:get/get.dart'; class SearchPage extends StatefulWidget { const SearchPage({super.key}); @override State createState() => _SearchPageState(); } class _SearchPageState extends State { final _tag = Utils.generateRandomString(6); late final SSearchController _searchController; late ThemeData theme; late bool isPortrait; late EdgeInsets padding; @override void initState() { super.initState(); _searchController = Get.put( SSearchController(_tag), tag: _tag, ); } @override void didChangeDependencies() { super.didChangeDependencies(); theme = Theme.of(context); padding = MediaQuery.viewPaddingOf(context); isPortrait = MediaQuery.sizeOf(context).isPortrait; } @override Widget build(BuildContext context) { final trending = _buildHotSearch(); return scaffold( appBar: _buildAppBar, body: Padding( padding: .only(left: padding.left, right: padding.right), child: CustomScrollView( slivers: [ _buildSearchSuggest(), if (isPortrait) ...[ trending, _buildHistory, ] else SliverCrossAxisGroup( slivers: [trending, _buildHistory], ), SliverPadding(padding: .only(bottom: padding.bottom)), ], ), ), ); } PreferredSizeWidget get _buildAppBar => AppBar( shape: Border( bottom: BorderSide( color: theme.dividerColor.withValues(alpha: 0.08), width: 1, ), ), actions: [ Obx( () => _searchController.showUidBtn.value ? IconButton( tooltip: 'UID搜索用户', icon: const Icon(Icons.person_outline, size: 22), onPressed: () => Get.toNamed( '/member?mid=${_searchController.controller.text}', ), ) : const SizedBox.shrink(), ), IconButton( tooltip: '清空', icon: const Icon(Icons.clear, size: 22), onPressed: _searchController.onClear, ), IconButton( tooltip: '搜索', onPressed: _searchController.submit, icon: const Icon(Icons.search, size: 22), ), const SizedBox(width: 10), ], title: TextField( autofocus: true, focusNode: _searchController.searchFocusNode, controller: _searchController.controller, textInputAction: TextInputAction.search, onChanged: _searchController.onChange, decoration: InputDecoration( visualDensity: .standard, hintText: _searchController.hintText ?? '搜索', border: InputBorder.none, ), onSubmitted: (value) => _searchController.submit(), ), ); Widget _buildSearchSuggest() { return Obx(() { final list = _searchController.searchSuggestList; return list.isNotEmpty && list.first.term != null && _searchController.controller.text != '' ? SliverList.list( children: list .map( (item) => InkWell( borderRadius: const .all(.circular(4)), onTap: () => _searchController.onClickKeyword(item.term!), child: Padding( padding: const .only(left: 20, top: 9, bottom: 9), child: Text.rich( TextSpan( children: Em.regTitle(item.textRich) .map( (e) => TextSpan( text: e.text, style: e.isEm ? TextStyle( fontWeight: FontWeight.bold, color: Theme.of( context, ).colorScheme.primary, ) : null, ), ) .toList(), ), ), ), ), ) .toList(), ) : const SliverToBoxAdapter(); }); } Widget _buildHotSearch() { final text = Text( '大家都在搜', strutStyle: const StrutStyle(leading: 0, height: 1), style: theme.textTheme.titleMedium!.copyWith( height: 1, fontWeight: FontWeight.bold, ), ); final outline = theme.colorScheme.outline; final secondary = theme.colorScheme.secondary; final style = TextStyle( height: 1, fontSize: 13, color: outline, ); return SliverPadding( padding: EdgeInsets.fromLTRB( 10, isPortrait ? 4 : 25, 4, 25, ), sliver: SliverMainAxisGroup( slivers: [ SliverPadding( padding: const EdgeInsets.fromLTRB(6, 0, 6, 6), sliver: SliverToBoxAdapter( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ text, const SizedBox(width: 14), TextButton( style: const ButtonStyle( visualDensity: .compact, tapTargetSize: .shrinkWrap, padding: WidgetStatePropertyAll( .symmetric(horizontal: 10), ), ), onPressed: () => Get.toNamed('/searchTrending'), child: Row( children: [ Text( '完整榜单', strutStyle: const StrutStyle( leading: 0, height: 1, ), style: style, ), Icon( size: 18, Icons.keyboard_arrow_right, color: outline, ), ], ), ), ], ), TextButton.icon( style: const ButtonStyle( visualDensity: .compact, tapTargetSize: .shrinkWrap, padding: WidgetStatePropertyAll( .symmetric(horizontal: 10), ), ), onPressed: _searchController.queryTrendingList, icon: Icon( Icons.refresh_outlined, size: 18, color: secondary, ), label: Text( '刷新', strutStyle: const StrutStyle(leading: 0, height: 1), style: TextStyle( height: 1, color: secondary, ), ), ), ], ), ), ), Obx( () => _buildHotKey(_searchController.trendingState.value), ), ], ), ); } late final mainAxisExtent = 16 + MediaQuery.textScalerOf(context).scale(14); Widget get _buildHistory { return Obx( () { final list = _searchController.historyList; if (list.isEmpty) { return const SliverToBoxAdapter(); } final secondary = theme.colorScheme.secondary; return SliverPadding( padding: EdgeInsets.fromLTRB( 10, !isPortrait ? 25 : 0, 6, 25, ), sliver: SliverMainAxisGroup( slivers: [ SliverPadding( padding: const EdgeInsets.fromLTRB(6, 0, 6, 6), sliver: SliverToBoxAdapter( child: Row( mainAxisAlignment: .spaceBetween, children: [ Text( '搜索历史', strutStyle: const StrutStyle(leading: 0, height: 1), style: theme.textTheme.titleMedium!.copyWith( height: 1, fontWeight: FontWeight.bold, ), ), TextButton.icon( style: const ButtonStyle( visualDensity: .compact, tapTargetSize: .shrinkWrap, padding: WidgetStatePropertyAll( .symmetric(horizontal: 10), ), ), onPressed: _searchController.onClearHistory, icon: Icon( Icons.clear_all_outlined, size: 18, color: secondary, ), label: Text( '清空', style: TextStyle( height: 1, color: secondary, ), ), ), ], ), ), ), SliverFixedWrap( mainAxisExtent: mainAxisExtent, spacing: 8, runSpacing: 8, delegate: SliverChildBuilderDelegate( addAutomaticKeepAlives: false, addRepaintBoundaries: false, childCount: list.length, (context, index) => SearchText( text: list[index], onTap: _searchController.onClickKeyword, onLongPress: _searchController.onLongSelect, fontSize: 14, height: 1, padding: const EdgeInsets.symmetric( horizontal: 11, vertical: 8, ), ), ), ), ], ), ); }, ); } Widget _buildHotKey( LoadingState loadingState, ) { return switch (loadingState) { Success(:final response) when (response.list?.isNotEmpty ?? false) => SliverHotKeyword( hotSearchList: response.list!, onClick: _searchController.onClickKeyword, ), Error(:final errMsg) => HttpError( safeArea: false, errMsg: errMsg, onReload: _searchController.queryTrendingList, ), _ => const SliverToBoxAdapter(), }; } }