From 243ecb3f71925b36d3aed16aa5595c2d1a72208b Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 28 Jun 2025 00:47:03 +0800 Subject: [PATCH] :sparkles: Searchable realms --- assets/i18n/en-US.json | 3 +- lib/pods/userinfo.dart | 1 - lib/screens/discovery/realms.dart | 50 +++++++- lib/widgets/realm/realm_list.dart | 29 +++-- lib/widgets/realm/realm_list.g.dart | 181 +++++++++++++++++++++++++--- 5 files changed, 232 insertions(+), 32 deletions(-) diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 002ccc9..cfd0c6d 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -563,5 +563,6 @@ "realmJoin": "Join the Realm", "realmJoinSuccess": "Successfully joined the realm.", "discoverRealms": "Discover Realms", - "discoverPublishers": "Discover Publishers" + "discoverPublishers": "Discover Publishers", + "search": "Search" } diff --git a/lib/pods/userinfo.dart b/lib/pods/userinfo.dart index d1e1134..2106332 100644 --- a/lib/pods/userinfo.dart +++ b/lib/pods/userinfo.dart @@ -32,7 +32,6 @@ class UserInfoNotifier extends StateNotifier> { state = const AsyncValue.data(null); final prefs = _ref.read(sharedPreferencesProvider); await prefs.remove(kTokenPairStoreKey); - _ref.invalidate(userInfoProvider); _ref.invalidate(tokenProvider); } } diff --git a/lib/screens/discovery/realms.dart b/lib/screens/discovery/realms.dart index 312e4eb..7bbdda9 100644 --- a/lib/screens/discovery/realms.dart +++ b/lib/screens/discovery/realms.dart @@ -1,22 +1,62 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/realm/realm_list.dart'; +import 'dart:async'; class DiscoveryRealmsScreen extends HookConsumerWidget { const DiscoveryRealmsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + Timer? debounceTimer; + final searchController = useTextEditingController(); + final currentQuery = useState(null); + return AppScaffold( appBar: AppBar(title: Text('discoverRealms'.tr())), - body: CustomScrollView( - slivers: [ - SliverGap(16), - SliverRealmList(), - SliverGap(MediaQuery.of(context).padding.bottom + 16), + body: Stack( + children: [ + CustomScrollView( + slivers: [ + SliverGap(80), + SliverRealmList( + query: currentQuery.value, + key: ValueKey(currentQuery.value), + ), + SliverGap(MediaQuery.of(context).padding.bottom + 16), + ], + ), + Positioned( + top: 0, + left: 0, + right: 0, + 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; + } + }); + }, + ), + ), + ), ], ), ); diff --git a/lib/widgets/realm/realm_list.dart b/lib/widgets/realm/realm_list.dart index 6254793..aeecfc8 100644 --- a/lib/widgets/realm/realm_list.dart +++ b/lib/widgets/realm/realm_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/realm.dart'; import 'package:island/pods/network.dart'; @@ -14,16 +15,23 @@ class RealmListNotifier extends _$RealmListNotifier static const int _pageSize = 20; @override - Future> build() { - return fetch(cursor: null); + Future> build(String? query) { + return fetch(cursor: null, query: query); } @override - Future> fetch({required String? cursor}) async { + Future> fetch({ + required String? cursor, + String? query, + }) async { final client = ref.read(apiClientProvider); final offset = cursor == null ? 0 : int.parse(cursor); - final queryParams = {'offset': offset, 'take': _pageSize}; + final queryParams = { + 'offset': offset, + 'take': _pageSize, + if (query != null && query.isNotEmpty) 'query': query, + }; final response = await client.get( '/discovery/realms', @@ -45,16 +53,18 @@ class RealmListNotifier extends _$RealmListNotifier } class SliverRealmList extends HookConsumerWidget { - const SliverRealmList({super.key}); + const SliverRealmList({super.key, this.query}); + + final String? query; @override Widget build(BuildContext context, WidgetRef ref) { return PagingHelperSliverView( - provider: realmListNotifierProvider, - futureRefreshable: realmListNotifierProvider.future, - notifierRefreshable: realmListNotifierProvider.notifier, + provider: realmListNotifierProvider(query), + futureRefreshable: realmListNotifierProvider(query).future, + notifierRefreshable: realmListNotifierProvider(query).notifier, contentBuilder: - (data, widgetCount, endItemView) => SliverList.builder( + (data, widgetCount, endItemView) => SliverList.separated( itemCount: widgetCount, itemBuilder: (context, index) { if (index == widgetCount - 1) { @@ -71,6 +81,7 @@ class SliverRealmList extends HookConsumerWidget { child: RealmCard(realm: realm), ); }, + separatorBuilder: (_, _) => const Gap(8), ), ); } diff --git a/lib/widgets/realm/realm_list.g.dart b/lib/widgets/realm/realm_list.g.dart index 4fbd541..ca950aa 100644 --- a/lib/widgets/realm/realm_list.g.dart +++ b/lib/widgets/realm/realm_list.g.dart @@ -6,25 +6,174 @@ part of 'realm_list.dart'; // RiverpodGenerator // ************************************************************************** -String _$realmListNotifierHash() => r'440eb8c61db2059699191b904b6518a0b01ccd25'; +String _$realmListNotifierHash() => r'02dee373a5609a5617b04ffec395d09dea7ae070'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$RealmListNotifier + extends BuildlessAutoDisposeAsyncNotifier> { + late final String? query; + + FutureOr> build(String? query); +} /// See also [RealmListNotifier]. @ProviderFor(RealmListNotifier) -final realmListNotifierProvider = AutoDisposeAsyncNotifierProvider< - RealmListNotifier, - CursorPagingData ->.internal( - RealmListNotifier.new, - name: r'realmListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$realmListNotifierHash, - dependencies: null, - allTransitiveDependencies: null, -); +const realmListNotifierProvider = RealmListNotifierFamily(); + +/// See also [RealmListNotifier]. +class RealmListNotifierFamily + extends Family>> { + /// See also [RealmListNotifier]. + const RealmListNotifierFamily(); + + /// See also [RealmListNotifier]. + RealmListNotifierProvider call(String? query) { + return RealmListNotifierProvider(query); + } + + @override + RealmListNotifierProvider getProviderOverride( + covariant RealmListNotifierProvider provider, + ) { + return call(provider.query); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'realmListNotifierProvider'; +} + +/// See also [RealmListNotifier]. +class RealmListNotifierProvider + extends + AutoDisposeAsyncNotifierProviderImpl< + RealmListNotifier, + CursorPagingData + > { + /// See also [RealmListNotifier]. + RealmListNotifierProvider(String? query) + : this._internal( + () => RealmListNotifier()..query = query, + from: realmListNotifierProvider, + name: r'realmListNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$realmListNotifierHash, + dependencies: RealmListNotifierFamily._dependencies, + allTransitiveDependencies: + RealmListNotifierFamily._allTransitiveDependencies, + query: query, + ); + + RealmListNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.query, + }) : super.internal(); + + final String? query; + + @override + FutureOr> runNotifierBuild( + covariant RealmListNotifier notifier, + ) { + return notifier.build(query); + } + + @override + Override overrideWith(RealmListNotifier Function() create) { + return ProviderOverride( + origin: this, + override: RealmListNotifierProvider._internal( + () => create()..query = query, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + query: query, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement< + RealmListNotifier, + CursorPagingData + > + createElement() { + return _RealmListNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is RealmListNotifierProvider && other.query == query; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, query.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin RealmListNotifierRef + on AutoDisposeAsyncNotifierProviderRef> { + /// The parameter `query` of this provider. + String? get query; +} + +class _RealmListNotifierProviderElement + extends + AutoDisposeAsyncNotifierProviderElement< + RealmListNotifier, + CursorPagingData + > + with RealmListNotifierRef { + _RealmListNotifierProviderElement(super.provider); + + @override + String? get query => (origin as RealmListNotifierProvider).query; +} -typedef _$RealmListNotifier = - AutoDisposeAsyncNotifier>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package