From 2edfa7251b53cb9c2bc98d5f08376739cd635fc4 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 5 Nov 2025 07:58:06 +0100 Subject: [PATCH] better feed fetching --- .../controllers/website_title.dart | 45 ++++------ .../controllers/website_title.g.dart | 89 +++++++++++++++++++ ...ile.dart => website_feed_menu_button.dart} | 41 +++++---- 3 files changed, 126 insertions(+), 49 deletions(-) rename app/lib/presentation/widgets/{website_feed_tile.dart => website_feed_menu_button.dart} (67%) diff --git a/app/lib/presentation/controllers/website_title.dart b/app/lib/presentation/controllers/website_title.dart index a55e33e0..940bd7d6 100644 --- a/app/lib/presentation/controllers/website_title.dart +++ b/app/lib/presentation/controllers/website_title.dart @@ -17,6 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'package:fast_equatable/fast_equatable.dart'; import 'package:nullability/nullability.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -46,34 +47,6 @@ class CompletePageInfo extends _$CompletePageInfo { return AsyncData(cached); } - // ref.listen( - // fireImmediately: true, - // pageInfoProvider(cached.url, isImageRequest: false), - // (previous, next) { - // if (next.hasValue) { - // final current = stateOrNull?.value ?? cached; - - // state = AsyncData( - // current.copyWith( - // //Cached is preferred as this comes from gecko and is more likely to be correct compared to manual request - // favicon: current.favicon ?? next.value!.favicon, - // feeds: current.feeds ?? next.value!.feeds, - // title: current.title.whenNotEmpty ?? next.value!.title, - // ), - // ); - // } else { - // state = next; - // } - // }, - // onError: (error, stackTrace) { - // logger.e( - // 'Error listening to pageInfoProvider', - // error: error, - // stackTrace: stackTrace, - // ); - // }, - // ); - ref.listen( fireImmediately: true, tabStateProvider(cached.id).select((value) => value?.title), @@ -150,3 +123,19 @@ Future pageInfo( return result.value; } + +@Riverpod() +AsyncValue?>> websiteFeedProvider( + Ref ref, + String tabId, +) { + final tabState = ref.watch(tabStateProvider(tabId))!; + final feeds = ref.watch( + pageInfoProvider( + tabState.url, + isImageRequest: false, + ).select((value) => value.whenData((data) => EquatableValue(data.feeds))), + ); + + return feeds; +} diff --git a/app/lib/presentation/controllers/website_title.g.dart b/app/lib/presentation/controllers/website_title.g.dart index aefa4609..110c7a1c 100644 --- a/app/lib/presentation/controllers/website_title.g.dart +++ b/app/lib/presentation/controllers/website_title.g.dart @@ -189,3 +189,92 @@ final class PageInfoFamily extends $Family @override String toString() => r'pageInfoProvider'; } + +@ProviderFor(websiteFeedProvider) +const websiteFeedProviderProvider = WebsiteFeedProviderFamily._(); + +final class WebsiteFeedProviderProvider + extends + $FunctionalProvider< + AsyncValue?>>, + AsyncValue?>>, + AsyncValue?>> + > + with $Provider?>>> { + const WebsiteFeedProviderProvider._({ + required WebsiteFeedProviderFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'websiteFeedProviderProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$websiteFeedProviderHash(); + + @override + String toString() { + return r'websiteFeedProviderProvider' + '' + '($argument)'; + } + + @$internal + @override + $ProviderElement?>>> $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + AsyncValue?>> create(Ref ref) { + final argument = this.argument as String; + return websiteFeedProvider(ref, argument); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AsyncValue?>> value) { + return $ProviderOverride( + origin: this, + providerOverride: + $SyncValueProvider?>>>(value), + ); + } + + @override + bool operator ==(Object other) { + return other is WebsiteFeedProviderProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$websiteFeedProviderHash() => + r'b8ad81b883acee41f420af9aeca254fbd29b3107'; + +final class WebsiteFeedProviderFamily extends $Family + with + $FunctionalFamilyOverride< + AsyncValue?>>, + String + > { + const WebsiteFeedProviderFamily._() + : super( + retry: null, + name: r'websiteFeedProviderProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + WebsiteFeedProviderProvider call(String tabId) => + WebsiteFeedProviderProvider._(argument: tabId, from: this); + + @override + String toString() => r'websiteFeedProviderProvider'; +} diff --git a/app/lib/presentation/widgets/website_feed_tile.dart b/app/lib/presentation/widgets/website_feed_menu_button.dart similarity index 67% rename from app/lib/presentation/widgets/website_feed_tile.dart rename to app/lib/presentation/widgets/website_feed_menu_button.dart index 2269d11b..687c3dff 100644 --- a/app/lib/presentation/widgets/website_feed_tile.dart +++ b/app/lib/presentation/widgets/website_feed_menu_button.dart @@ -24,47 +24,47 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nullability/nullability.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:weblibre/core/routing/routes.dart'; -import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/presentation/controllers/website_title.dart'; import 'package:weblibre/presentation/widgets/rounded_text.dart'; -class WebsiteFeedTile extends HookConsumerWidget { - final TabState initialTabState; +class WebsiteFeedMenuButton extends HookConsumerWidget { + final String tabId; - const WebsiteFeedTile(this.initialTabState, {super.key}); + const WebsiteFeedMenuButton(this.tabId, {super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final pageInfoAsync = ref.watch(completePageInfoProvider(initialTabState)); + final feedsAsync = ref.watch(websiteFeedProviderProvider(tabId)); return Skeletonizer( - enabled: pageInfoAsync.isLoading && initialTabState.feeds == null, - child: pageInfoAsync.when( + enabled: feedsAsync.isLoading && feedsAsync.value?.value == null, + child: feedsAsync.when( skipLoadingOnReload: true, - data: (info) { - if (info.feeds.isEmpty) { + data: (feeds) { + if (feeds.value.isEmpty) { return const SizedBox.shrink(); } - return ListTile( - leading: const Icon(Icons.rss_feed), - title: const Text('Available Web Feeds'), - trailing: RoundedBackground( + return MenuItemButton( + leadingIcon: const Icon(Icons.rss_feed), + closeOnActivate: false, + trailingIcon: RoundedBackground( child: Text( - info.feeds!.length.toString(), + feeds.value!.length.toString(), textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context).colorScheme.onPrimary, ), ), ), - onTap: () async { + onPressed: () async { await SelectFeedDialogRoute( feedsJson: jsonEncode( - info.feeds!.map((feed) => feed.toString()).toList(), + feeds.value!.map((feed) => feed.toString()).toList(), ), ).push(context); }, + child: const Text('Available Web Feeds'), ); }, error: (error, stackTrace) { @@ -77,11 +77,10 @@ class WebsiteFeedTile extends HookConsumerWidget { // onRetry: () => ref.refresh(pageInfoProvider(url)), // ); }, - loading: () => const ListTile( - leading: Icon(Icons.rss_feed), - contentPadding: EdgeInsets.zero, - title: Text('Available Web Feeds'), - trailing: Bone.icon(), + loading: () => const MenuItemButton( + leadingIcon: Icon(Icons.rss_feed), + trailingIcon: Bone.icon(), + child: Text('Available Web Feeds'), ), ), );