better feed fetching

This commit is contained in:
Fabian Freund
2025-11-05 07:58:06 +01:00
parent 2bfb3fdf87
commit 2edfa7251b
3 changed files with 126 additions and 49 deletions
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<WebPageInfo> pageInfo(
return result.value;
}
@Riverpod()
AsyncValue<EquatableValue<Set<Uri>?>> 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;
}
@@ -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<EquatableValue<Set<Uri>?>>,
AsyncValue<EquatableValue<Set<Uri>?>>,
AsyncValue<EquatableValue<Set<Uri>?>>
>
with $Provider<AsyncValue<EquatableValue<Set<Uri>?>>> {
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<AsyncValue<EquatableValue<Set<Uri>?>>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
AsyncValue<EquatableValue<Set<Uri>?>> create(Ref ref) {
final argument = this.argument as String;
return websiteFeedProvider(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AsyncValue<EquatableValue<Set<Uri>?>> value) {
return $ProviderOverride(
origin: this,
providerOverride:
$SyncValueProvider<AsyncValue<EquatableValue<Set<Uri>?>>>(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<EquatableValue<Set<Uri>?>>,
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';
}
@@ -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'),
),
),
);