diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index ffa45caa..e6c701fc 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -1581,5 +1581,7 @@ "followingEmptyHint": "Start by searching for users or explore other instances", "fediversePost": "Fediverse Post", "fediversePostDescribe": "Post from the Fediverse Network", - "settingsShowFediverseContent": "Show Fediverse Content" + "settingsShowFediverseContent": "Show Fediverse Content", + "universalSearch": "Universal Search", + "universalSearchDescription": "Search content across the Solar Network and the fediverse network." } \ No newline at end of file diff --git a/lib/models/route_item.dart b/lib/models/route_item.dart index 703ea4f9..42b77cc9 100644 --- a/lib/models/route_item.dart +++ b/lib/models/route_item.dart @@ -32,10 +32,10 @@ final List kAvailableRoutes = [ icon: Symbols.explore, ), RouteItem( - name: 'searchPosts'.tr(), - path: '/posts/search', - description: 'searchPostsDescription'.tr(), - searchableAliases: ['search', 'posts'], + name: 'universalSearch'.tr(), + path: '/search', + description: 'universalSearchDescription'.tr(), + searchableAliases: ['search', 'universal', 'fediverse'], icon: Symbols.search, ), RouteItem( diff --git a/lib/route.dart b/lib/route.dart index 5ce60106..10c6af39 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -7,7 +7,6 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/screens/about.dart'; import 'package:island/screens/activitypub/list.dart'; -import 'package:island/screens/activitypub/search.dart'; import 'package:island/screens/dashboard/dash.dart'; import 'package:island/screens/developers/app_detail.dart'; import 'package:island/screens/developers/bot_detail.dart'; @@ -20,7 +19,6 @@ import 'package:island/screens/files/file_list.dart'; import 'package:island/screens/files/file_detail.dart'; import 'package:island/screens/posts/post_categories_list.dart'; import 'package:island/screens/posts/post_category_detail.dart'; -import 'package:island/screens/posts/post_search.dart'; import 'package:island/screens/search.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_wrapper.dart'; @@ -194,11 +192,6 @@ final routerProvider = Provider((ref) { builder: (context, state) => const UniversalSearchScreen(), ), - GoRoute( - name: 'activitypubSearch', - path: '/activitypub/search', - builder: (context, state) => const ApSearchScreen(), - ), GoRoute( name: 'activitypubFollowing', path: '/activitypub/following', @@ -239,11 +232,6 @@ final routerProvider = Provider((ref) { transitionsBuilder: _tabPagesTransitionBuilder, ), ), - GoRoute( - name: 'postSearch', - path: '/posts/search', - builder: (context, state) => const PostSearchScreen(), - ), GoRoute( name: 'postShuffle', path: '/posts/shuffle', diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index 1f3b20ab..268e4c38 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -519,11 +519,7 @@ class ChatListScreen extends HookConsumerWidget { ), ); }, - ).padding( - bottom: - (isWideScreen(context) ? 0 : 56) + - MediaQuery.of(context).padding.bottom, - ) + ).padding(bottom: MediaQuery.of(context).padding.bottom) : null, appBar: AppBar( flexibleSpace: Container( diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 0cc488db..eee15f90 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -255,11 +255,7 @@ class ExploreScreen extends HookConsumerWidget { ), ); }, - ).padding( - bottom: - (isWideScreen(context) ? 0 : 56) + - MediaQuery.of(context).padding.bottom, - ) + ).padding(bottom: MediaQuery.of(context).padding.bottom) : null, body: isWide ? _buildWideBody( diff --git a/lib/screens/posts/post_search.dart b/lib/screens/posts/post_search.dart deleted file mode 100644 index 6056a69e..00000000 --- a/lib/screens/posts/post_search.dart +++ /dev/null @@ -1,305 +0,0 @@ -import 'dart:async'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/post_item_skeleton.dart'; -import 'package:island/widgets/posts/post_filter.dart'; -import 'package:gap/gap.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:material_symbols_icons/symbols.dart'; -import 'package:styled_widget/styled_widget.dart'; - -const kSearchPostListId = 'search'; - -class PostSearchScreen extends HookConsumerWidget { - const PostSearchScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final searchController = useTextEditingController(); - final debounce = useMemoized(() => Duration(milliseconds: 500)); - final debounceTimer = useRef(null); - final showFilters = useState(false); - final pubNameController = useTextEditingController(); - final realmController = useTextEditingController(); - - // State variables for PostFilterWidget - final categoryTabController = useTabController(initialLength: 3); - - // Single query state - final queryState = useState(const PostListQuery()); - - final noti = ref.read( - postListProvider(PostListQueryConfig(id: kSearchPostListId)).notifier, - ); - - useEffect(() { - return () { - searchController.dispose(); - pubNameController.dispose(); - realmController.dispose(); - debounceTimer.value?.cancel(); - }; - }, []); - - void onSearchChanged(String query, {bool skipDebounce = false}) { - queryState.value = queryState.value.copyWith(queryTerm: query); - - if (skipDebounce) { - noti.applyFilter(queryState.value); - return; - } - - if (debounceTimer.value?.isActive ?? false) debounceTimer.value!.cancel(); - debounceTimer.value = Timer(debounce, () { - noti.applyFilter(queryState.value); - }); - } - - void toggleFilterDisplay() { - showFilters.value = !showFilters.value; - } - - Widget buildFilterPanel() { - return PostFilterWidget( - categoryTabController: categoryTabController, - initialQuery: queryState.value, - onQueryChanged: (newQuery) { - queryState.value = newQuery; - noti.applyFilter(newQuery); - }, - hideSearch: true, - ); - } - - return AppScaffold( - isNoBackground: false, - appBar: AppBar( - title: Text('searchPosts'.tr()), - actions: [ - if (!isWideScreen(context)) - IconButton( - icon: Icon( - showFilters.value - ? Icons.filter_alt - : Icons.filter_alt_outlined, - ), - onPressed: toggleFilterDisplay, - tooltip: 'toggleFilters'.tr(), - ), - ], - ), - body: Consumer( - builder: (context, ref, child) { - final searchState = ref.watch( - postListProvider(PostListQueryConfig(id: kSearchPostListId)), - ); - - return isWideScreen(context) - ? Row( - children: [ - Flexible( - flex: 4, - child: ExtendedRefreshIndicator( - onRefresh: noti.refresh, - child: CustomScrollView( - slivers: [ - SliverGap(16), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - 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: onSearchChanged, - onSubmitted: (value) { - onSearchChanged(value, skipDebounce: true); - }, - ), - ), - ), - const SliverGap(12), - PaginationList( - provider: postListProvider( - PostListQueryConfig(id: kSearchPostListId), - ), - notifier: postListProvider( - PostListQueryConfig(id: kSearchPostListId), - ).notifier, - isSliver: true, - isRefreshable: false, - footerSkeletonChild: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: const PostItemSkeleton(), - ), - itemBuilder: (context, index, post) { - return Card( - margin: EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - child: PostActionableItem( - item: post, - borderRadius: 8, - ), - ); - }, - ), - if (searchState.value?.items.isEmpty == true && - searchController.text.isNotEmpty && - !searchState.isLoading) - SliverFillRemaining( - child: Center( - child: Text('noResultsFound'.tr()), - ), - ), - SliverGap( - MediaQuery.of(context).padding.bottom + 16, - ), - ], - ).padding(left: 8), - ), - ), - Flexible( - flex: 3, - child: Align( - alignment: Alignment.topLeft, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Gap(16), - Card( - margin: EdgeInsets.symmetric(horizontal: 8), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - child: Row( - children: [ - const Icon( - Symbols.tune, - ).padding(horizontal: 8), - Expanded( - child: Text( - 'filters'.tr(), - style: Theme.of( - context, - ).textTheme.bodyLarge, - ), - ), - IconButton( - icon: Icon( - Symbols.filter_alt, - fill: showFilters.value ? 1 : null, - ), - onPressed: toggleFilterDisplay, - tooltip: 'toggleFilters'.tr(), - ), - const Gap(4), - ], - ), - ), - ), - const Gap(8), - if (showFilters.value) buildFilterPanel(), - ], - ), - ), - ), - ), - ], - ) - : CustomScrollView( - slivers: [ - const SliverGap(4), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - 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: onSearchChanged, - onSubmitted: (value) { - onSearchChanged(value, skipDebounce: true); - }, - ), - ), - ), - if (showFilters.value) - SliverToBoxAdapter( - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 600), - child: buildFilterPanel(), - ), - ), - ), - PaginationList( - provider: postListProvider( - PostListQueryConfig(id: kSearchPostListId), - ), - notifier: postListProvider( - PostListQueryConfig(id: kSearchPostListId), - ).notifier, - isSliver: true, - isRefreshable: false, - footerSkeletonChild: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: const PostItemSkeleton(), - ), - itemBuilder: (context, index, post) { - return Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 600), - child: Card( - margin: EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - child: PostActionableItem( - item: post, - borderRadius: 8, - ), - ), - ), - ); - }, - ), - if (searchState.value?.items.isEmpty == true && - searchController.text.isNotEmpty && - !searchState.isLoading) - SliverFillRemaining( - child: Center(child: Text('noResultsFound'.tr())), - ), - ], - ); - }, - ), - ); - } -} diff --git a/lib/screens/realm/realms.dart b/lib/screens/realm/realms.dart index fe52b78b..2063fffb 100644 --- a/lib/screens/realm/realms.dart +++ b/lib/screens/realm/realms.dart @@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/realm.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; -import 'package:island/services/responsive.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; @@ -115,11 +114,7 @@ class RealmListScreen extends HookConsumerWidget { ), ); }, - ).padding( - bottom: - (isWideScreen(context) ? 0 : 56) + - MediaQuery.of(context).padding.bottom, - ) + ).padding(bottom: MediaQuery.of(context).padding.bottom) : null, body: userInfo.value == null ? const ResponseUnauthorizedWidget() diff --git a/lib/widgets/post/post_shared.dart b/lib/widgets/post/post_shared.dart index 83a95844..3284203d 100644 --- a/lib/widgets/post/post_shared.dart +++ b/lib/widgets/post/post_shared.dart @@ -67,7 +67,6 @@ class RepliesNotifier extends _$RepliesNotifier { ); if (!ref.mounted) return; - state = state.copyWith( posts: [...state.posts, ...response.data.map((e) => SnPost.fromJson(e))], loading: false, @@ -160,7 +159,9 @@ class PostReplyPreview extends HookConsumerWidget { if (isAutoload) { Future(() async { try { - await ref.read(repliesProvider(parent.id).notifier).fetchMore(3); + if (context.mounted) { + await ref.read(repliesProvider(parent.id).notifier).fetchMore(3); + } } catch (err) { showErrorAlert(err); }