From cfd2a47064e8a7cfa604729bbfdeb969fed4062e Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 2 Feb 2026 13:30:33 +0800 Subject: [PATCH] :recycle: Optimize realm discovery --- lib/route.dart | 13 +++++- lib/screens/realm/realms.dart | 2 +- lib/screens/search.dart | 66 +++++++++++++++++++++++++++++-- lib/widgets/realm/realm_list.dart | 9 ++--- 4 files changed, 80 insertions(+), 10 deletions(-) diff --git a/lib/route.dart b/lib/route.dart index 0b9eb4d7..61b225e0 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -233,7 +233,18 @@ final routerProvider = Provider((ref) { GoRoute( name: 'universalSearch', path: '/search', - builder: (context, state) => const UniversalSearchScreen(), + builder: (context, state) { + final initialTab = state.uri.queryParameters['tab']; + SearchTab tab; + if (initialTab == 'realms') { + tab = SearchTab.realms; + } else if (initialTab == 'fediverse') { + tab = SearchTab.fediverse; + } else { + tab = SearchTab.posts; + } + return UniversalSearchScreen(initialTab: tab); + }, ), // Main tabs with TabsScreen shell diff --git a/lib/screens/realm/realms.dart b/lib/screens/realm/realms.dart index c9ab4d10..80a07eec 100644 --- a/lib/screens/realm/realms.dart +++ b/lib/screens/realm/realms.dart @@ -51,7 +51,7 @@ class RealmListScreen extends HookConsumerWidget { actions: [ IconButton( icon: const Icon(Symbols.travel_explore), - onPressed: () => context.pushNamed('discoveryRealms'), + onPressed: () => context.pushNamed('universalSearch', queryParameters: {'tab': 'realms'}), ), IconButton( icon: Badge( diff --git a/lib/screens/search.dart b/lib/screens/search.dart index ae50472b..0bc530c9 100644 --- a/lib/screens/search.dart +++ b/lib/screens/search.dart @@ -16,12 +16,13 @@ import 'package:island/widgets/paging/pagination_list.dart'; import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/post_item_skeleton.dart'; import 'package:island/widgets/post/filters/post_filter.dart'; +import 'package:island/widgets/realm/realm_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; const kSearchPostListId = 'search'; -enum SearchTab { posts, fediverse } +enum SearchTab { posts, fediverse, realms } class UniversalSearchScreen extends HookConsumerWidget { final SearchTab initialTab; @@ -31,7 +32,7 @@ class UniversalSearchScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final tabController = useTabController( - initialLength: 2, + initialLength: 3, initialIndex: initialTab.index, ); @@ -45,12 +46,13 @@ class UniversalSearchScreen extends HookConsumerWidget { tabs: [ Tab(text: 'posts'.tr()), Tab(text: 'fediverseUsers'.tr()), + Tab(text: 'realms'.tr()), ], ), Expanded( child: TabBarView( controller: tabController, - children: [_PostsSearchTab(), _FediverseSearchTab()], + children: [_PostsSearchTab(), _FediverseSearchTab(), _RealmsSearchTab()], ), ), ], @@ -59,6 +61,64 @@ class UniversalSearchScreen extends HookConsumerWidget { } } +class _RealmsSearchTab extends HookConsumerWidget { + const _RealmsSearchTab(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + Timer? debounceTimer; + final searchController = useTextEditingController(); + final currentQuery = useState(null); + + return Stack( + children: [ + CustomScrollView( + slivers: [ + const SliverGap(88), + SliverRealmList( + query: currentQuery.value, + key: ValueKey(currentQuery.value), + ), + SliverGap(MediaQuery.of(context).padding.bottom + 16), + ], + ), + Positioned( + top: 0, + left: 0, + right: 0, + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 560), + child: Padding( + padding: const EdgeInsets.all(16), + child: SearchBar( + elevation: WidgetStateProperty.all(4), + controller: searchController, + hintText: 'search'.tr(), + leading: const Icon(Icons.search), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric(horizontal: 24), + ), + onChanged: (value) { + if (debounceTimer?.isActive ?? false) { + debounceTimer?.cancel(); + } + debounceTimer = Timer(const Duration(milliseconds: 300), () { + if (currentQuery.value != value) { + currentQuery.value = value; + } + }); + }, + ), + ), + ), + ), + ), + ], + ); + } +} + class _PostsSearchTab extends HookConsumerWidget { const _PostsSearchTab(); diff --git a/lib/widgets/realm/realm_list.dart b/lib/widgets/realm/realm_list.dart index 5b5f51c0..4ad55f97 100644 --- a/lib/widgets/realm/realm_list.dart +++ b/lib/widgets/realm/realm_list.dart @@ -9,10 +9,9 @@ import 'package:island/widgets/paging/pagination_list.dart'; import 'package:island/widgets/realm/realm_list_tile.dart'; import 'package:styled_widget/styled_widget.dart'; -final realmListNotifierProvider = AsyncNotifierProvider.autoDispose - .family, String?>( - RealmListNotifier.new, - ); +final realmListNotifierProvider = AsyncNotifierProvider.autoDispose.family( + RealmListNotifier.new, +); class RealmListNotifier extends AsyncNotifier> with AsyncPaginationController { @@ -45,7 +44,7 @@ class RealmListNotifier extends AsyncNotifier> }; final response = await client.get( - '/sphere/discovery/realms', + '/pass/realms/public', queryParameters: queryParams, ); totalCount = int.parse(response.headers.value('X-Total') ?? '0');