prepare for multiple apps
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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 'dart:async';
|
||||
|
||||
import 'package:nullability/nullability.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_parse_result.dart';
|
||||
import 'package:weblibre/features/web_feed/data/providers.dart';
|
||||
import 'package:weblibre/features/web_feed/domain/providers/article_filter.dart';
|
||||
import 'package:weblibre/features/web_feed/domain/repositories/feed_repository.dart';
|
||||
import 'package:weblibre/features/web_feed/domain/services/feed_reader.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class ArticleSearch extends _$ArticleSearch {
|
||||
late StreamController<List<FeedArticle>> _streamController;
|
||||
|
||||
Future<void> search(
|
||||
String input, {
|
||||
int snippetLength = 120,
|
||||
int maxResults = 25,
|
||||
String matchPrefix = '***',
|
||||
String matchSuffix = '***',
|
||||
String ellipsis = '…',
|
||||
}) async {
|
||||
if (input.isNotEmpty) {
|
||||
await ref
|
||||
.read(feedDatabaseProvider)
|
||||
.articleDao
|
||||
.queryArticles(
|
||||
matchPrefix: matchPrefix,
|
||||
matchSuffix: matchSuffix,
|
||||
ellipsis: ellipsis,
|
||||
snippetLength: snippetLength,
|
||||
searchString: input,
|
||||
feedId: feedId,
|
||||
limit: maxResults,
|
||||
)
|
||||
.get()
|
||||
.then((value) {
|
||||
if (!_streamController.isClosed) {
|
||||
_streamController.add(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<FeedArticle>> build(Uri? feedId) {
|
||||
_streamController = StreamController();
|
||||
|
||||
ref.onDispose(() async {
|
||||
await _streamController.close();
|
||||
});
|
||||
|
||||
return ConcatStream([Stream.value([]), _streamController.stream]);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<FeedData>> feedList(Ref ref) {
|
||||
final repository = ref.watch(feedRepositoryProvider.notifier);
|
||||
return repository.watchFeeds();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<FeedData?> feedData(Ref ref, Uri? feedId) {
|
||||
final repository = ref.watch(feedRepositoryProvider.notifier);
|
||||
|
||||
if (feedId == null) {
|
||||
return Stream.value(null);
|
||||
}
|
||||
|
||||
return repository.watchFeed(feedId);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<FeedArticle>> feedArticleList(Ref ref, Uri? feedId) {
|
||||
final repository = ref.watch(feedRepositoryProvider.notifier);
|
||||
return repository.watchFeedArticles(feedId);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
class FilteredArticleList extends _$FilteredArticleList {
|
||||
bool _hasSearch = false;
|
||||
|
||||
void search(String input) {
|
||||
if (input.isNotEmpty) {
|
||||
if (!_hasSearch) {
|
||||
_hasSearch = true;
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
//Don't block
|
||||
unawaited(ref.read(articleSearchProvider(feedId).notifier).search(input));
|
||||
} else if (_hasSearch) {
|
||||
_hasSearch = false;
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
AsyncValue<List<FeedArticle>> build(Uri? feedId) {
|
||||
final filterTags = ref.watch(articleFilterProvider);
|
||||
|
||||
final articlesAsync = _hasSearch
|
||||
? ref.watch(articleSearchProvider(feedId))
|
||||
: ref.watch(feedArticleListProvider(feedId));
|
||||
|
||||
return articlesAsync.whenData((articles) {
|
||||
if (filterTags.isNotEmpty) {
|
||||
return articles.where((article) {
|
||||
final tags = article.tags?.map((tag) => tag.id).toSet();
|
||||
|
||||
final authors = article.authors
|
||||
?.map((author) => author.name.whenNotEmpty)
|
||||
.nonNulls
|
||||
.toSet();
|
||||
|
||||
return filterTags.every(
|
||||
(filter) =>
|
||||
(tags?.contains(filter) ?? false) ||
|
||||
(authors?.contains(filter) ?? false),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return articles;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<FeedArticle?> feedArticle(
|
||||
Ref ref,
|
||||
String articleId, {
|
||||
required bool updateReadDate,
|
||||
}) async* {
|
||||
final repository = ref.watch(feedRepositoryProvider.notifier);
|
||||
|
||||
if (updateReadDate) {
|
||||
await repository.touchArticleRead(articleId);
|
||||
}
|
||||
|
||||
yield* repository.watchArticle(articleId);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Raw<Stream<Map<String, int>>> unreadArticleCount(Ref ref) {
|
||||
final repository = ref.watch(feedRepositoryProvider.notifier);
|
||||
return repository.watchUnreadFeedArticleCount();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<int?> unreadFeedArticleCount(Ref ref, Uri feedId) {
|
||||
final stream = ref.watch(unreadArticleCountProvider);
|
||||
return stream.map((counts) => counts[feedId.toString()]);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Future<FeedParseResult> fetchWebFeed(Ref ref, Uri url) {
|
||||
return ref.read(feedReaderProvider.notifier).parseFeed(url);
|
||||
}
|
||||
@@ -0,0 +1,678 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ArticleSearch)
|
||||
final articleSearchProvider = ArticleSearchFamily._();
|
||||
|
||||
final class ArticleSearchProvider
|
||||
extends $StreamNotifierProvider<ArticleSearch, List<FeedArticle>> {
|
||||
ArticleSearchProvider._({
|
||||
required ArticleSearchFamily super.from,
|
||||
required Uri? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'articleSearchProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$articleSearchHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'articleSearchProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ArticleSearch create() => ArticleSearch();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ArticleSearchProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$articleSearchHash() => r'48ed3baa560c0e731626f79a8f6ff3dab1e9bc95';
|
||||
|
||||
final class ArticleSearchFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<
|
||||
ArticleSearch,
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
List<FeedArticle>,
|
||||
Stream<List<FeedArticle>>,
|
||||
Uri?
|
||||
> {
|
||||
ArticleSearchFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'articleSearchProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
ArticleSearchProvider call(Uri? feedId) =>
|
||||
ArticleSearchProvider._(argument: feedId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'articleSearchProvider';
|
||||
}
|
||||
|
||||
abstract class _$ArticleSearch extends $StreamNotifier<List<FeedArticle>> {
|
||||
late final _$args = ref.$arg as Uri?;
|
||||
Uri? get feedId => _$args;
|
||||
|
||||
Stream<List<FeedArticle>> build(Uri? feedId);
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref =
|
||||
this.ref as $Ref<AsyncValue<List<FeedArticle>>, List<FeedArticle>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<List<FeedArticle>>, List<FeedArticle>>,
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, () => build(_$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(feedList)
|
||||
final feedListProvider = FeedListProvider._();
|
||||
|
||||
final class FeedListProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<FeedData>>,
|
||||
List<FeedData>,
|
||||
Stream<List<FeedData>>
|
||||
>
|
||||
with $FutureModifier<List<FeedData>>, $StreamProvider<List<FeedData>> {
|
||||
FeedListProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'feedListProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$feedListHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<List<FeedData>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<List<FeedData>> create(Ref ref) {
|
||||
return feedList(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$feedListHash() => r'0076186437354768c39fb1d7c8bcfcf7b94c7dd1';
|
||||
|
||||
@ProviderFor(feedData)
|
||||
final feedDataProvider = FeedDataFamily._();
|
||||
|
||||
final class FeedDataProvider
|
||||
extends
|
||||
$FunctionalProvider<AsyncValue<FeedData?>, FeedData?, Stream<FeedData?>>
|
||||
with $FutureModifier<FeedData?>, $StreamProvider<FeedData?> {
|
||||
FeedDataProvider._({
|
||||
required FeedDataFamily super.from,
|
||||
required Uri? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'feedDataProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$feedDataHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'feedDataProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<FeedData?> $createElement($ProviderPointer pointer) =>
|
||||
$StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<FeedData?> create(Ref ref) {
|
||||
final argument = this.argument as Uri?;
|
||||
return feedData(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FeedDataProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$feedDataHash() => r'0599a2e3d159ef3abb6c3d2c871f87da2f5e646b';
|
||||
|
||||
final class FeedDataFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<FeedData?>, Uri?> {
|
||||
FeedDataFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'feedDataProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
FeedDataProvider call(Uri? feedId) =>
|
||||
FeedDataProvider._(argument: feedId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'feedDataProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(feedArticleList)
|
||||
final feedArticleListProvider = FeedArticleListFamily._();
|
||||
|
||||
final class FeedArticleListProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
List<FeedArticle>,
|
||||
Stream<List<FeedArticle>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<FeedArticle>>,
|
||||
$StreamProvider<List<FeedArticle>> {
|
||||
FeedArticleListProvider._({
|
||||
required FeedArticleListFamily super.from,
|
||||
required Uri? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'feedArticleListProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$feedArticleListHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'feedArticleListProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<List<FeedArticle>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<List<FeedArticle>> create(Ref ref) {
|
||||
final argument = this.argument as Uri?;
|
||||
return feedArticleList(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FeedArticleListProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$feedArticleListHash() => r'45d585cc9f59ad48a0d1d6fbcf802b1c7de7f6bc';
|
||||
|
||||
final class FeedArticleListFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<List<FeedArticle>>, Uri?> {
|
||||
FeedArticleListFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'feedArticleListProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
FeedArticleListProvider call(Uri? feedId) =>
|
||||
FeedArticleListProvider._(argument: feedId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'feedArticleListProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(FilteredArticleList)
|
||||
final filteredArticleListProvider = FilteredArticleListFamily._();
|
||||
|
||||
final class FilteredArticleListProvider
|
||||
extends
|
||||
$NotifierProvider<FilteredArticleList, AsyncValue<List<FeedArticle>>> {
|
||||
FilteredArticleListProvider._({
|
||||
required FilteredArticleListFamily super.from,
|
||||
required Uri? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'filteredArticleListProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$filteredArticleListHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'filteredArticleListProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
FilteredArticleList create() => FilteredArticleList();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(AsyncValue<List<FeedArticle>> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<AsyncValue<List<FeedArticle>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FilteredArticleListProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$filteredArticleListHash() =>
|
||||
r'a691c3c6aa722dd85ee4980e6c48721a8025f1d8';
|
||||
|
||||
final class FilteredArticleListFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<
|
||||
FilteredArticleList,
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
Uri?
|
||||
> {
|
||||
FilteredArticleListFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'filteredArticleListProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
FilteredArticleListProvider call(Uri? feedId) =>
|
||||
FilteredArticleListProvider._(argument: feedId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'filteredArticleListProvider';
|
||||
}
|
||||
|
||||
abstract class _$FilteredArticleList
|
||||
extends $Notifier<AsyncValue<List<FeedArticle>>> {
|
||||
late final _$args = ref.$arg as Uri?;
|
||||
Uri? get feedId => _$args;
|
||||
|
||||
AsyncValue<List<FeedArticle>> build(Uri? feedId);
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref =
|
||||
this.ref
|
||||
as $Ref<
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
AsyncValue<List<FeedArticle>>
|
||||
>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
AsyncValue<List<FeedArticle>>
|
||||
>,
|
||||
AsyncValue<List<FeedArticle>>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, () => build(_$args));
|
||||
}
|
||||
}
|
||||
|
||||
@ProviderFor(feedArticle)
|
||||
final feedArticleProvider = FeedArticleFamily._();
|
||||
|
||||
final class FeedArticleProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<FeedArticle?>,
|
||||
FeedArticle?,
|
||||
Stream<FeedArticle?>
|
||||
>
|
||||
with $FutureModifier<FeedArticle?>, $StreamProvider<FeedArticle?> {
|
||||
FeedArticleProvider._({
|
||||
required FeedArticleFamily super.from,
|
||||
required (String, {bool updateReadDate}) super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'feedArticleProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$feedArticleHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'feedArticleProvider'
|
||||
''
|
||||
'$argument';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<FeedArticle?> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<FeedArticle?> create(Ref ref) {
|
||||
final argument = this.argument as (String, {bool updateReadDate});
|
||||
return feedArticle(
|
||||
ref,
|
||||
argument.$1,
|
||||
updateReadDate: argument.updateReadDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FeedArticleProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$feedArticleHash() => r'18b5faf391867b95f4b3bc4f74a6083854b633a5';
|
||||
|
||||
final class FeedArticleFamily extends $Family
|
||||
with
|
||||
$FunctionalFamilyOverride<
|
||||
Stream<FeedArticle?>,
|
||||
(String, {bool updateReadDate})
|
||||
> {
|
||||
FeedArticleFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'feedArticleProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
FeedArticleProvider call(String articleId, {required bool updateReadDate}) =>
|
||||
FeedArticleProvider._(
|
||||
argument: (articleId, updateReadDate: updateReadDate),
|
||||
from: this,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() => r'feedArticleProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(unreadArticleCount)
|
||||
final unreadArticleCountProvider = UnreadArticleCountProvider._();
|
||||
|
||||
final class UnreadArticleCountProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
Raw<Stream<Map<String, int>>>,
|
||||
Raw<Stream<Map<String, int>>>,
|
||||
Raw<Stream<Map<String, int>>>
|
||||
>
|
||||
with $Provider<Raw<Stream<Map<String, int>>>> {
|
||||
UnreadArticleCountProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'unreadArticleCountProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$unreadArticleCountHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<Raw<Stream<Map<String, int>>>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Raw<Stream<Map<String, int>>> create(Ref ref) {
|
||||
return unreadArticleCount(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Raw<Stream<Map<String, int>>> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Raw<Stream<Map<String, int>>>>(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$unreadArticleCountHash() =>
|
||||
r'709518ad229636df0f1095f47e3a6d116b3aa7e6';
|
||||
|
||||
@ProviderFor(unreadFeedArticleCount)
|
||||
final unreadFeedArticleCountProvider = UnreadFeedArticleCountFamily._();
|
||||
|
||||
final class UnreadFeedArticleCountProvider
|
||||
extends $FunctionalProvider<AsyncValue<int?>, int?, Stream<int?>>
|
||||
with $FutureModifier<int?>, $StreamProvider<int?> {
|
||||
UnreadFeedArticleCountProvider._({
|
||||
required UnreadFeedArticleCountFamily super.from,
|
||||
required Uri super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'unreadFeedArticleCountProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$unreadFeedArticleCountHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'unreadFeedArticleCountProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$StreamProviderElement<int?> $createElement($ProviderPointer pointer) =>
|
||||
$StreamProviderElement(pointer);
|
||||
|
||||
@override
|
||||
Stream<int?> create(Ref ref) {
|
||||
final argument = this.argument as Uri;
|
||||
return unreadFeedArticleCount(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is UnreadFeedArticleCountProvider &&
|
||||
other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$unreadFeedArticleCountHash() =>
|
||||
r'5e0d8e58b3d1dec978dc07b22367f2cb037b1b15';
|
||||
|
||||
final class UnreadFeedArticleCountFamily extends $Family
|
||||
with $FunctionalFamilyOverride<Stream<int?>, Uri> {
|
||||
UnreadFeedArticleCountFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'unreadFeedArticleCountProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
UnreadFeedArticleCountProvider call(Uri feedId) =>
|
||||
UnreadFeedArticleCountProvider._(argument: feedId, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'unreadFeedArticleCountProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(fetchWebFeed)
|
||||
final fetchWebFeedProvider = FetchWebFeedFamily._();
|
||||
|
||||
final class FetchWebFeedProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<FeedParseResult>,
|
||||
FeedParseResult,
|
||||
FutureOr<FeedParseResult>
|
||||
>
|
||||
with $FutureModifier<FeedParseResult>, $FutureProvider<FeedParseResult> {
|
||||
FetchWebFeedProvider._({
|
||||
required FetchWebFeedFamily super.from,
|
||||
required Uri super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'fetchWebFeedProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$fetchWebFeedHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'fetchWebFeedProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<FeedParseResult> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<FeedParseResult> create(Ref ref) {
|
||||
final argument = this.argument as Uri;
|
||||
return fetchWebFeed(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FetchWebFeedProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$fetchWebFeedHash() => r'73bdf87ad7dbd039c7dc181d80acdf99d96fe1c6';
|
||||
|
||||
final class FetchWebFeedFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<FeedParseResult>, Uri> {
|
||||
FetchWebFeedFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'fetchWebFeedProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
FetchWebFeedProvider call(Uri url) =>
|
||||
FetchWebFeedProvider._(argument: url, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'fetchWebFeedProvider';
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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:nullability/nullability.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
|
||||
part 'add_dialog_blocking.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class AddFeedDialogBlocking extends _$AddFeedDialogBlocking {
|
||||
DateTime? _lastIgnore;
|
||||
final _ignoredUrls = <Uri, DateTime>{};
|
||||
|
||||
void ignore(Uri url) {
|
||||
final date = DateTime.now();
|
||||
|
||||
_lastIgnore = date;
|
||||
_ignoredUrls[url] = date;
|
||||
}
|
||||
|
||||
bool canPush(Uri url) {
|
||||
if (_lastIgnore.mapNotNull(
|
||||
(last) =>
|
||||
DateTime.now().difference(last) <= const Duration(seconds: 30),
|
||||
) ??
|
||||
false) {
|
||||
logger.i('Blocking add feed default timeout for $url');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_ignoredUrls[url].mapNotNull(
|
||||
(last) =>
|
||||
DateTime.now().difference(last) <= const Duration(minutes: 5),
|
||||
) ??
|
||||
false) {
|
||||
logger.i('Blocking add feed url specific for $url');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'add_dialog_blocking.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(AddFeedDialogBlocking)
|
||||
final addFeedDialogBlockingProvider = AddFeedDialogBlockingProvider._();
|
||||
|
||||
final class AddFeedDialogBlockingProvider
|
||||
extends $NotifierProvider<AddFeedDialogBlocking, void> {
|
||||
AddFeedDialogBlockingProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'addFeedDialogBlockingProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$addFeedDialogBlockingHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
AddFeedDialogBlocking create() => AddFeedDialogBlocking();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(void value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<void>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$addFeedDialogBlockingHash() =>
|
||||
r'513071d5e507dd292df6b4a3ee3ef7d2217db5bb';
|
||||
|
||||
abstract class _$AddFeedDialogBlocking extends $Notifier<void> {
|
||||
void build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<void, void>,
|
||||
void,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'article_filter.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ArticleFilter extends _$ArticleFilter {
|
||||
void addTag(String tagId) {
|
||||
state = {...state, tagId};
|
||||
}
|
||||
|
||||
void removeTag(String tagId) {
|
||||
if (state.isNotEmpty) {
|
||||
state = {...state}..remove(tagId);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Set<String> build() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'article_filter.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ArticleFilter)
|
||||
final articleFilterProvider = ArticleFilterProvider._();
|
||||
|
||||
final class ArticleFilterProvider
|
||||
extends $NotifierProvider<ArticleFilter, Set<String>> {
|
||||
ArticleFilterProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'articleFilterProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$articleFilterHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ArticleFilter create() => ArticleFilter();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Set<String> value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Set<String>>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$articleFilterHash() => r'61e4d5230e214038e753bc173158ed1d4dc57040';
|
||||
|
||||
abstract class _$ArticleFilter extends $Notifier<Set<String>> {
|
||||
Set<String> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<Set<String>, Set<String>>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<Set<String>, Set<String>>,
|
||||
Set<String>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/features/web_feed/data/database/definitions.drift.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
import 'package:weblibre/features/web_feed/data/providers.dart';
|
||||
|
||||
part 'feed_repository.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class FeedRepository extends _$FeedRepository {
|
||||
Future<List<FeedData>> getAllFeeds() {
|
||||
return ref.read(feedDatabaseProvider).feedDao.getFeeds().get();
|
||||
}
|
||||
|
||||
Future<void> touchFeedFetched(Uri feedId) {
|
||||
return ref
|
||||
.read(feedDatabaseProvider)
|
||||
.feedDao
|
||||
.updateFeedFetched(feedId, DateTime.now());
|
||||
}
|
||||
|
||||
Future<void> upsertFeed(FeedData feedData) {
|
||||
return ref.read(feedDatabaseProvider).feedDao.upsertFeed(feedData);
|
||||
}
|
||||
|
||||
Future<void> upsertArticles(List<FeedArticle> articles) {
|
||||
return ref.read(feedDatabaseProvider).articleDao.upsertArticles(articles);
|
||||
}
|
||||
|
||||
Future<int> deleteFeed(Uri feedId) {
|
||||
return ref.read(feedDatabaseProvider).feedDao.deleteFeed(feedId);
|
||||
}
|
||||
|
||||
Future<void> touchArticleRead(String articleId) {
|
||||
return ref
|
||||
.read(feedDatabaseProvider)
|
||||
.articleDao
|
||||
.updateArticleRead(articleId, DateTime.now());
|
||||
}
|
||||
|
||||
Future<void> unsetArticleRead(String articleId) {
|
||||
return ref
|
||||
.read(feedDatabaseProvider)
|
||||
.articleDao
|
||||
.updateArticleRead(articleId, null);
|
||||
}
|
||||
|
||||
Stream<List<FeedData>> watchFeeds() {
|
||||
return ref.read(feedDatabaseProvider).feedDao.getFeeds().watch();
|
||||
}
|
||||
|
||||
Stream<FeedData?> watchFeed(Uri feedId) {
|
||||
return ref
|
||||
.read(feedDatabaseProvider)
|
||||
.feedDao
|
||||
.getFeed(feedId)
|
||||
.watchSingleOrNull();
|
||||
}
|
||||
|
||||
Stream<List<FeedArticle>> watchFeedArticles(Uri? feedId) {
|
||||
return ref
|
||||
.read(feedDatabaseProvider)
|
||||
.articleDao
|
||||
.getFeedArticles(feedId)
|
||||
.watch();
|
||||
}
|
||||
|
||||
Stream<FeedArticle?> watchArticle(String articleId) {
|
||||
return ref
|
||||
.read(feedDatabaseProvider)
|
||||
.articleDao
|
||||
.getArticleById(articleId)
|
||||
.watchSingleOrNull();
|
||||
}
|
||||
|
||||
Stream<Map<String, int>> watchUnreadFeedArticleCount() {
|
||||
return ref
|
||||
.read(feedDatabaseProvider)
|
||||
.articleDao
|
||||
.getUnreadArticleCount()
|
||||
.watch()
|
||||
.map(
|
||||
(results) =>
|
||||
Map.fromEntries(results.map((e) => MapEntry(e.$1, e.$2))),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed_repository.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(FeedRepository)
|
||||
final feedRepositoryProvider = FeedRepositoryProvider._();
|
||||
|
||||
final class FeedRepositoryProvider
|
||||
extends $NotifierProvider<FeedRepository, void> {
|
||||
FeedRepositoryProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'feedRepositoryProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$feedRepositoryHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
FeedRepository create() => FeedRepository();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(void value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<void>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$feedRepositoryHash() => r'805cc26890b0d43576a1eab0ecb5851b6649d7b7';
|
||||
|
||||
abstract class _$FeedRepository extends $Notifier<void> {
|
||||
void build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<void, void>,
|
||||
void,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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:collection/collection.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_article.dart';
|
||||
import 'package:weblibre/features/web_feed/data/providers.dart';
|
||||
|
||||
part 'article_content_processor.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ArticleContentProcessorService extends _$ArticleContentProcessorService {
|
||||
@override
|
||||
void build() {
|
||||
final db = ref.watch(feedDatabaseProvider);
|
||||
|
||||
final processSub = db.articleDao.getUnprocessedArticles().watch().listen((
|
||||
articles,
|
||||
) async {
|
||||
try {
|
||||
final content = await GeckoBrowserExtensionService.turndownHtml(
|
||||
articles.map((article) => article.contentHtml ?? '').toList(),
|
||||
);
|
||||
final summary = await GeckoBrowserExtensionService.turndownHtml(
|
||||
articles.map((article) => article.summaryHtml ?? '').toList(),
|
||||
);
|
||||
|
||||
await db.articleDao.updateArticleContent(
|
||||
articles
|
||||
.mapIndexed(
|
||||
(index, article) => article.copyWith(
|
||||
contentMarkdown: content[index].markdown ?? '',
|
||||
contentPlain: content[index].plain,
|
||||
summaryMarkdown: summary[index].markdown ?? '',
|
||||
summaryPlain: summary[index].plain,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
logger.i('Processed ${articles.length} articles');
|
||||
} catch (e, s) {
|
||||
logger.e('Error processing articles', error: e, stackTrace: s);
|
||||
}
|
||||
});
|
||||
|
||||
ref.onDispose(() async {
|
||||
await processSub.cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'article_content_processor.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ArticleContentProcessorService)
|
||||
final articleContentProcessorServiceProvider =
|
||||
ArticleContentProcessorServiceProvider._();
|
||||
|
||||
final class ArticleContentProcessorServiceProvider
|
||||
extends $NotifierProvider<ArticleContentProcessorService, void> {
|
||||
ArticleContentProcessorServiceProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'articleContentProcessorServiceProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$articleContentProcessorServiceHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ArticleContentProcessorService create() => ArticleContentProcessorService();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(void value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<void>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$articleContentProcessorServiceHash() =>
|
||||
r'e7cc3da71f6dcf39b0c10df4dcd061f816d5c12e';
|
||||
|
||||
abstract class _$ArticleContentProcessorService extends $Notifier<void> {
|
||||
void build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<void, void>,
|
||||
void,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/extensions/http_encoding.dart';
|
||||
import 'package:weblibre/features/web_feed/data/models/feed_parse_result.dart';
|
||||
import 'package:weblibre/features/web_feed/utils/feed_parser.dart';
|
||||
|
||||
part 'feed_reader.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class FeedReader extends _$FeedReader {
|
||||
Future<FeedParseResult> parseFeed(Uri url) async {
|
||||
final rootIsolateToken = ServicesBinding.rootIsolateToken!;
|
||||
|
||||
final result = await compute((args) async {
|
||||
// Initialize BackgroundIsolateBinaryMessenger with the token
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(
|
||||
args['token']! as RootIsolateToken,
|
||||
);
|
||||
|
||||
final client = http.Client();
|
||||
try {
|
||||
final url = Uri.parse(args['url']! as String);
|
||||
final response = await client
|
||||
.get(url)
|
||||
.timeout(const Duration(seconds: 30));
|
||||
|
||||
final parser = FeedParser.parse(
|
||||
url: url,
|
||||
xmlString: response.bodyUnicodeFallback,
|
||||
);
|
||||
final result = FeedParseResult(
|
||||
feedData: parser.readGeneralData(),
|
||||
articleData: parser.readArticles(),
|
||||
);
|
||||
|
||||
return result.toJson();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}, {'token': rootIsolateToken, 'url': url.toString()});
|
||||
|
||||
return FeedParseResult.fromJson(result);
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'feed_reader.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(FeedReader)
|
||||
final feedReaderProvider = FeedReaderProvider._();
|
||||
|
||||
final class FeedReaderProvider extends $NotifierProvider<FeedReader, void> {
|
||||
FeedReaderProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'feedReaderProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$feedReaderHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
FeedReader create() => FeedReader();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(void value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<void>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$feedReaderHash() => r'5d1ca364fe7ad702628a7f2bbd3e706876bc3111';
|
||||
|
||||
abstract class _$FeedReader extends $Notifier<void> {
|
||||
void build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<void, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<void, void>,
|
||||
void,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user