background fetch running

This commit is contained in:
Fabian Freund
2025-03-05 14:27:13 +01:00
parent 20bdc4df3e
commit 70d1c1b50e
62 changed files with 898 additions and 173 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"dart.runPubGetOnPubspecChanges": "never"
}
+1
View File
@@ -6,6 +6,7 @@ allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven { url "${project(':background_fetch').projectDir}/libs" }
} }
} }
+2 -4
View File
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:lensai/features/bangs/data/database/database.dart'; import 'package:lensai/features/bangs/data/database/database.dart';
@@ -38,8 +36,8 @@ BangDatabase bangDatabase(Ref ref) {
}), }),
); );
ref.onDispose(() { ref.onDispose(() async {
unawaited(db.close()); await db.close();
}); });
return db; return db;
+1 -1
View File
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$bangDatabaseHash() => r'1ff3fb8bfa29269f6e515f3b22c216998769f578'; String _$bangDatabaseHash() => r'e31cf4b499300aff65174a16fb74c7dd8ec3d6f0';
/// See also [bangDatabase]. /// See also [bangDatabase].
@ProviderFor(bangDatabase) @ProviderFor(bangDatabase)
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:lensai/data/database/functions/lexo_rank_functions.dart'; import 'package:lensai/data/database/functions/lexo_rank_functions.dart';
@@ -44,8 +42,8 @@ ChatDatabase chatDatabase(Ref ref) {
}), }),
); );
ref.onDispose(() { ref.onDispose(() async {
unawaited(db.close()); await db.close();
}); });
return db; return db;
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$chatDatabaseHash() => r'b48955bc016f8c74684ed6e2c5cba99f5ab38dfe'; String _$chatDatabaseHash() => r'd07fef426c6984e6fb2eab9651a5c51aaca98961';
/// See also [chatDatabase]. /// See also [chatDatabase].
@ProviderFor(chatDatabase) @ProviderFor(chatDatabase)
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/domain/providers.dart'; import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -25,8 +23,8 @@ class SelectedTab extends _$SelectedTab {
state = tabId; state = tabId;
}); });
ref.onDispose(() { ref.onDispose(() async {
unawaited(selectedTabSub.cancel()); await selectedTabSub.cancel();
}); });
return null; return null;
@@ -6,7 +6,7 @@ part of 'selected_tab.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$selectedTabHash() => r'250f54fe9a56d14876e0979e5370eb76879da11c'; String _$selectedTabHash() => r'12b670dd76c3416e8d62bafd63bf69a83a603e3a';
/// See also [SelectedTab]. /// See also [SelectedTab].
@ProviderFor(SelectedTab) @ProviderFor(SelectedTab)
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/domain/providers.dart'; import 'package:lensai/features/geckoview/domain/providers.dart';
@@ -30,8 +28,8 @@ class TabList extends _$TabList {
} }
}); });
ref.onDispose(() { ref.onDispose(() async {
unawaited(tabListSub.cancel()); await tabListSub.cancel();
}); });
return EquatableValue([]); return EquatableValue([]);
@@ -6,7 +6,7 @@ part of 'tab_list.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$tabListHash() => r'2b98ccdf62c15670e0a12e38064e8186e6555cf8'; String _$tabListHash() => r'9507bcc75dc69a55aba14334cdef0128a5fcf252';
/// See also [TabList]. /// See also [TabList].
@ProviderFor(TabList) @ProviderFor(TabList)
@@ -165,9 +165,9 @@ class TabStates extends _$TabStates {
} }
}); });
ref.onDispose(() { ref.onDispose(() async {
for (final sub in subscriptions) { for (final sub in subscriptions) {
unawaited(sub.cancel()); await sub.cancel();
} }
}); });
@@ -160,7 +160,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>; typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
String _$tabStatesHash() => r'b136dbf6d68b67f0fe0db302a4147b67789db853'; String _$tabStatesHash() => r'3a906aec8c4e650de8d5d2461ff45a99b8a0a3b9';
/// See also [TabStates]. /// See also [TabStates].
@ProviderFor(TabStates) @ProviderFor(TabStates)
@@ -86,9 +86,9 @@ class WebExtensionsState extends _$WebExtensionsState {
], ],
}; };
ref.onDispose(() { ref.onDispose(() async {
for (final sub in subscriptions) { for (final sub in subscriptions) {
unawaited(sub.cancel()); await sub.cancel();
} }
}); });
@@ -7,7 +7,7 @@ part of 'web_extensions_state.dart';
// ************************************************************************** // **************************************************************************
String _$webExtensionsStateHash() => String _$webExtensionsStateHash() =>
r'eab924e9c17e805a3c29065289be5d1c8ffe3bc9'; r'6fb44ab543250f2b2554e410ee749fdd035cd4e7';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {
@@ -167,10 +167,10 @@ class TabRepository extends _$TabRepository {
}); });
}); });
ref.onDispose(() { ref.onDispose(() async {
tabStateDebouncer.dispose(); tabStateDebouncer.dispose();
unawaited(tabAddedSub.cancel()); await tabAddedSub.cancel();
unawaited(tabContentSub.cancel()); await tabContentSub.cancel();
}); });
} }
} }
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$tabRepositoryHash() => r'69e283b820d86388f4d80e71e08761e2b6890507'; String _$tabRepositoryHash() => r'35c078a841d815aa2452c07b52c4cf0cbcec7e68';
/// See also [TabRepository]. /// See also [TabRepository].
@ProviderFor(TabRepository) @ProviderFor(TabRepository)
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/domain/repositories/data.dart'; import 'package:lensai/features/bangs/domain/repositories/data.dart';
@@ -48,8 +46,8 @@ class SelectedBangData extends _$SelectedBangData {
state = value; state = value;
}); });
ref.onDispose(() { ref.onDispose(() async {
unawaited(subscription.cancel()); await subscription.cancel();
}); });
return null; return null;
@@ -719,7 +719,7 @@ class _SelectedBangTriggerProviderElement
String? get domain => (origin as SelectedBangTriggerProvider).domain; String? get domain => (origin as SelectedBangTriggerProvider).domain;
} }
String _$selectedBangDataHash() => r'28c394e5989e108870ca5733e916b80b3cf8972c'; String _$selectedBangDataHash() => r'666858556efe59d2de38a04fda30e8ec227c6675';
abstract class _$SelectedBangData abstract class _$SelectedBangData
extends BuildlessAutoDisposeNotifier<BangData?> { extends BuildlessAutoDisposeNotifier<BangData?> {
@@ -4,10 +4,10 @@ import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/tor/domain/services/tor_proxy.dart'; import 'package:lensai/features/tor/domain/services/tor_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'proxy_settings_replucation.g.dart'; part 'proxy_settings_replication.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
class ProxySettingsReplucation extends _$ProxySettingsReplucation { class ProxySettingsReplication extends _$ProxySettingsReplication {
final _service = GeckoContainerProxyService(); final _service = GeckoContainerProxyService();
@override @override
@@ -1,28 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'proxy_settings_replucation.dart'; part of 'proxy_settings_replication.dart';
// ************************************************************************** // **************************************************************************
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$proxySettingsReplucationHash() => String _$proxySettingsReplicationHash() =>
r'b00fc9a9c8b385a0f8a5830e9ba1c329d340c0ae'; r'1982923eeb0bcd6d606683fde70835ec7aff601b';
/// See also [ProxySettingsReplucation]. /// See also [ProxySettingsReplication].
@ProviderFor(ProxySettingsReplucation) @ProviderFor(ProxySettingsReplication)
final proxySettingsReplucationProvider = final proxySettingsReplicationProvider =
NotifierProvider<ProxySettingsReplucation, void>.internal( NotifierProvider<ProxySettingsReplication, void>.internal(
ProxySettingsReplucation.new, ProxySettingsReplication.new,
name: r'proxySettingsReplucationProvider', name: r'proxySettingsReplicationProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') const bool.fromEnvironment('dart.vm.product')
? null ? null
: _$proxySettingsReplucationHash, : _$proxySettingsReplicationHash,
dependencies: null, dependencies: null,
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
typedef _$ProxySettingsReplucation = Notifier<void>; typedef _$ProxySettingsReplication = Notifier<void>;
// ignore_for_file: type=lint // 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 // 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
@@ -15,11 +15,12 @@ import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.
import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/browser_data.dart'; import 'package:lensai/features/geckoview/features/browser/domain/services/browser_data.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings_replication.dart'; import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart'; import 'package:lensai/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart'; import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
import 'package:lensai/features/user/domain/repositories/cache.dart'; import 'package:lensai/features/user/domain/repositories/cache.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart'; import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:lensai/features/user/domain/services/local_authentication.dart'; import 'package:lensai/features/user/domain/services/local_authentication.dart';
import 'package:lensai/features/web_feed/domain/services/article_content_processor.dart';
class BrowserView extends StatefulHookConsumerWidget { class BrowserView extends StatefulHookConsumerWidget {
final Duration screenshotPeriod; final Duration screenshotPeriod;
@@ -151,7 +152,12 @@ class _BrowserViewState extends ConsumerState<BrowserView>
(previous, next) {}, (previous, next) {},
); );
ref.listenManual(proxySettingsReplucationProvider, (previous, next) {}); ref.listenManual(proxySettingsReplicationProvider, (previous, next) {});
ref.listenManual(
articleContentProcessorServiceProvider,
(previous, next) {},
);
} }
@override @override
@@ -68,8 +68,8 @@ class _PreferenceRepository extends _$PreferenceRepository {
Raw<Stream<Map<String, Object>>> build() { Raw<Stream<Map<String, Object>>> build() {
_prefSubject = BehaviorSubject<Map<String, Object>>(); _prefSubject = BehaviorSubject<Map<String, Object>>();
ref.onDispose(() { ref.onDispose(() async {
unawaited(_prefSubject.close()); await _prefSubject.close();
}); });
ref.onAddListener(() async { ref.onAddListener(() async {
@@ -330,7 +330,7 @@ class _PreferenceSettingGroupProviderElement
} }
String _$preferenceRepositoryHash() => String _$preferenceRepositoryHash() =>
r'53c0cbbf60e44c3834fe3e3255836f7e27f09c7f'; r'b2b7f75b3d30842b3006bb18af89f7ff90ba4ce7';
/// See also [_PreferenceRepository]. /// See also [_PreferenceRepository].
@ProviderFor(_PreferenceRepository) @ProviderFor(_PreferenceRepository)
@@ -9,6 +9,7 @@ import 'package:lensai/features/geckoview/domain/controllers/bottom_sheet.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart'; import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_field.dart'; import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_field.dart';
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart';
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart'; import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart';
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/search_suggestions.dart'; import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/search_suggestions.dart';
import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart'; import 'package:lensai/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart';
@@ -116,6 +117,7 @@ class SearchScreen extends HookConsumerWidget {
), ),
const SliverToBoxAdapter(child: Divider()), const SliverToBoxAdapter(child: Divider()),
TabSearch(searchTextController: searchTextController), TabSearch(searchTextController: searchTextController),
FeedSearch(searchTextController: searchTextController),
HistorySuggestions(searchTextController: searchTextController), HistorySuggestions(searchTextController: searchTextController),
], ],
), ),
@@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/extensions/uri.dart';
import 'package:lensai/features/web_feed/data/models/feed_article_query_result.dart';
import 'package:lensai/features/web_feed/data/models/feed_link.dart';
import 'package:lensai/features/web_feed/domain/providers.dart';
import 'package:lensai/features/web_feed/extensions/atom.dart';
import 'package:lensai/features/web_feed/extensions/feed_article.dart';
import 'package:lensai/presentation/hooks/listenable_callback.dart';
import 'package:lensai/presentation/widgets/failure_widget.dart';
import 'package:lensai/presentation/widgets/url_icon.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:sliver_tools/sliver_tools.dart';
class FeedSearch extends HookConsumerWidget {
static const _matchPrefix = '***';
static const _matchSuffix = '***';
final TextEditingController searchTextController;
const FeedSearch({required this.searchTextController});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final articlesAsync = ref.watch(articleSearchProvider(null));
useListenableCallback(searchTextController, () async {
await ref
.read(articleSearchProvider(null).notifier)
.search(
searchTextController.text,
// ignore: avoid_redundant_argument_values dont break things
matchPrefix: _matchPrefix,
// ignore: avoid_redundant_argument_values dont break things
matchSuffix: _matchSuffix,
);
});
if (articlesAsync.hasValue && (articlesAsync.valueOrNull.isEmpty)) {
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
return MultiSliver(
children: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Articles', style: Theme.of(context).textTheme.labelSmall),
],
),
),
),
SliverSkeletonizer(
enabled: articlesAsync.isLoading,
child: articlesAsync.when(
data:
(articles) => SliverList.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
final article = articles[index];
final titleHighlight = switch (article) {
final FeedArticleQueryResult result =>
result.titleHighlight.whenNotEmpty,
_ => null,
};
final searchSnippet = switch (article) {
final FeedArticleQueryResult result =>
result.summarySnippet.whenNotEmpty ??
result.contentSnippet.whenNotEmpty,
_ => null,
};
return ListTile(
leading: RepaintBoundary(
child: UrlIcon([
article.icon ??
article.links
?.getRelation(FeedLinkRelation.alternate)
?.uri ??
article.siteLink ??
article.feedId.base,
], iconSize: 24.0),
),
title:
(titleHighlight.isNotEmpty)
? MarkdownBody(
data: titleHighlight!,
styleSheet: MarkdownStyleSheet(
p: Theme.of(
context,
).textTheme.titleMedium?.copyWith(
color:
Theme.of(context).colorScheme.onSurface,
),
),
)
: Text(
article.displayTitle,
style: theme.textTheme.titleMedium,
),
subtitle:
(searchSnippet.isNotEmpty)
? MarkdownBody(
data: searchSnippet!,
styleSheet: MarkdownStyleSheet(
p: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(
color:
Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
a: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(
color:
Theme.of(
context,
).colorScheme.onSurfaceVariant,
decoration: TextDecoration.none,
),
),
)
: ((article.summaryPlain != null)
? Text(
article.summaryPlain!,
style: theme.textTheme.bodySmall,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: null),
onTap: () {
FeedArticleRoute(
articleId: article.id,
).pushReplacement(context);
},
);
},
),
error: (error, stackTrace) {
return SliverToBoxAdapter(
child: FailureWidget(
title: 'Failed searching Articles',
exception: error,
),
);
},
loading:
() => SliverList.builder(
itemCount: articlesAsync.valueOrNull?.length ?? 3,
itemBuilder: (context, index) {
return const ListTile(title: Bone.text());
},
),
),
),
],
);
}
}
@@ -20,7 +20,7 @@ class HistorySuggestions extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final historySuggestions = ref.watch(engineHistorySuggestionsProvider); final historySuggestionsAsync = ref.watch(engineHistorySuggestionsProvider);
useListenableCallback(searchTextController, () async { useListenableCallback(searchTextController, () async {
await ref await ref
@@ -28,8 +28,8 @@ class HistorySuggestions extends HookConsumerWidget {
.addQuery(searchTextController.text); .addQuery(searchTextController.text);
}); });
if (historySuggestions.hasValue && if (historySuggestionsAsync.hasValue &&
(historySuggestions.valueOrNull.isEmpty)) { (historySuggestionsAsync.valueOrNull.isEmpty)) {
return const SliverToBoxAdapter(child: SizedBox.shrink()); return const SliverToBoxAdapter(child: SizedBox.shrink());
} }
@@ -46,8 +46,8 @@ class HistorySuggestions extends HookConsumerWidget {
), ),
), ),
SliverSkeletonizer( SliverSkeletonizer(
enabled: historySuggestions.isLoading, enabled: historySuggestionsAsync.isLoading,
child: historySuggestions.when( child: historySuggestionsAsync.when(
skipLoadingOnReload: true, skipLoadingOnReload: true,
data: (historySuggestions) { data: (historySuggestions) {
return SliverList.builder( return SliverList.builder(
@@ -109,14 +109,16 @@ class HistorySuggestions extends HookConsumerWidget {
); );
}, },
error: (error, stackTrace) { error: (error, stackTrace) {
return FailureWidget( return SliverToBoxAdapter(
title: 'Could not load history', child: FailureWidget(
exception: error, title: 'Could not load history',
exception: error,
),
); );
}, },
loading: loading:
() => SliverList.builder( () => SliverList.builder(
itemCount: historySuggestions.valueOrNull?.length ?? 3, itemCount: historySuggestionsAsync.valueOrNull?.length ?? 3,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return const ListTile(title: Bone.text()); return const ListTile(title: Bone.text());
}, },
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:lensai/core/providers/models.dart'; import 'package:lensai/core/providers/models.dart';
@@ -51,8 +49,8 @@ TabDatabase tabDatabase(Ref ref) {
embeddingDimensions: dimensions, embeddingDimensions: dimensions,
); );
ref.onDispose(() { ref.onDispose(() async {
unawaited(db.close()); await db.close();
}); });
return db; return db;
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$tabDatabaseHash() => r'940dd2a1f1df6f2a0b77a1a3e9b8318550b832d2'; String _$tabDatabaseHash() => r'db190f01d7e70e65b9f44403effa82ec503144a0';
/// See also [tabDatabase]. /// See also [tabDatabase].
@ProviderFor(tabDatabase) @ProviderFor(tabDatabase)
@@ -108,8 +108,8 @@ class DocumentRepository extends _$DocumentRepository {
} }
}); });
ref.onDispose(() { ref.onDispose(() async {
unawaited(tabContentSub.cancel()); await tabContentSub.cancel();
}); });
} }
} }
@@ -7,7 +7,7 @@ part of 'document.dart';
// ************************************************************************** // **************************************************************************
String _$documentRepositoryHash() => String _$documentRepositoryHash() =>
r'60e5a0c889d9659c8e0e254c59225c2041a0cb56'; r'fd78b5eaf96951724e905b068144dcf2a254dcbb';
/// See also [DocumentRepository]. /// See also [DocumentRepository].
@ProviderFor(DocumentRepository) @ProviderFor(DocumentRepository)
@@ -71,8 +71,8 @@ class TorProxyService extends _$TorProxyService {
await _tor.initializeService(); await _tor.initializeService();
ref.onDispose(() { ref.onDispose(() async {
unawaited(portSub.cancel()); await portSub.cancel();
_tor.dispose(); _tor.dispose();
}); });
@@ -6,7 +6,7 @@ part of 'tor_proxy.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$torProxyServiceHash() => r'85ed8e3b49007aa56e6b82ff802a3e0ce567a0c8'; String _$torProxyServiceHash() => r'782708b8e5159931cecf8d32b843cb96a929fb1f';
/// See also [TorProxyService]. /// See also [TorProxyService].
@ProviderFor(TorProxyService) @ProviderFor(TorProxyService)
+2 -4
View File
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:lensai/features/user/data/database/database.dart'; import 'package:lensai/features/user/data/database/database.dart';
@@ -38,8 +36,8 @@ UserDatabase userDatabase(Ref ref) {
}), }),
); );
ref.onDispose(() { ref.onDispose(() async {
unawaited(db.close()); await db.close();
}); });
return db; return db;
+1 -1
View File
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$userDatabaseHash() => r'717de293750f5da91dc7da3f964bd07f5dd61e10'; String _$userDatabaseHash() => r'b925780435806f0241d7ddb635e301e9fc8baf6e';
/// See also [userDatabase]. /// See also [userDatabase].
@ProviderFor(userDatabase) @ProviderFor(userDatabase)
@@ -85,8 +85,8 @@ class EngineSettingsRepository extends _$EngineSettingsRepository {
}); });
}); });
ref.onDispose(() { ref.onDispose(() async {
unawaited(watchSub.cancel()); await watchSub.cancel();
}); });
return EngineSettings.withDefaults(); return EngineSettings.withDefaults();
@@ -7,7 +7,7 @@ part of 'engine_settings.dart';
// ************************************************************************** // **************************************************************************
String _$engineSettingsRepositoryHash() => String _$engineSettingsRepositoryHash() =>
r'940680d6d59a4a4a496589ac02eeab5a22779e5e'; r'bd255f91f7cee702d9cfcb10bfdf3cd013234901';
/// See also [EngineSettingsRepository]. /// See also [EngineSettingsRepository].
@ProviderFor(EngineSettingsRepository) @ProviderFor(EngineSettingsRepository)
@@ -74,8 +74,8 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
state = _deserializeSettings(event); state = _deserializeSettings(event);
}); });
ref.onDispose(() { ref.onDispose(() async {
unawaited(watchSub.cancel()); await watchSub.cancel();
}); });
return GeneralSettings.withDefaults(); return GeneralSettings.withDefaults();
@@ -7,7 +7,7 @@ part of 'general_settings.dart';
// ************************************************************************** // **************************************************************************
String _$generalSettingsRepositoryHash() => String _$generalSettingsRepositoryHash() =>
r'fd40dc270c52b137a5459f93f93c60e4c1ba9b58'; r'001ffa37f5afe1d2d3ee2b3ce8539836af76cf89';
/// See also [GeneralSettingsRepository]. /// See also [GeneralSettingsRepository].
@ProviderFor(GeneralSettingsRepository) @ProviderFor(GeneralSettingsRepository)
@@ -24,10 +24,45 @@ class ArticleDao extends DatabaseAccessor<FeedDatabase> with _$ArticleDaoMixin {
]); ]);
} }
Selectable<FeedArticle> getUnprocessedArticles() {
return db.articleView.select()..where(
(article) =>
(article.contentHtml.isNotNull() &
(article.contentMarkdown.isNull() |
article.contentPlain.isNull())) |
(article.summaryHtml.isNotNull() &
(article.summaryMarkdown.isNull() |
article.summaryPlain.isNull())),
);
}
SingleOrNullSelectable<FeedArticle> getArticleById(String articleId) { SingleOrNullSelectable<FeedArticle> getArticleById(String articleId) {
return db.articleView.select()..where((row) => row.id.equals(articleId)); return db.articleView.select()..where((row) => row.id.equals(articleId));
} }
Future<void> updateArticleContent(List<FeedArticle> articles) {
return db.transaction(() async {
await Future.wait(
articles.map((newArticle) {
final statement =
db.article.update()
..where((article) => article.id.equals(newArticle.id));
return statement.write(
ArticleCompanion(
summaryHtml: Value(newArticle.summaryHtml),
summaryMarkdown: Value(newArticle.summaryMarkdown),
summaryPlain: Value(newArticle.summaryPlain),
contentHtml: Value(newArticle.contentHtml),
contentMarkdown: Value(newArticle.contentMarkdown),
contentPlain: Value(newArticle.contentPlain),
),
);
}),
);
});
}
Future<void> upsertArticles(List<FeedArticle> articles) { Future<void> upsertArticles(List<FeedArticle> articles) {
return db.transaction(() async { return db.transaction(() async {
await Future.wait( await Future.wait(
@@ -39,9 +74,11 @@ class ArticleDao extends DatabaseAccessor<FeedDatabase> with _$ArticleDaoMixin {
(old) { (old) {
return ArticleCompanion( return ArticleCompanion(
authors: Value(article.authors), authors: Value(article.authors),
contentHtml: Value(article.contentHtml),
contentMarkdown: Value(article.contentMarkdown), contentMarkdown: Value(article.contentMarkdown),
contentPlain: Value(article.contentPlain), contentPlain: Value(article.contentPlain),
links: Value(article.links), links: Value(article.links),
summaryHtml: Value(article.summaryHtml),
summaryMarkdown: Value(article.summaryMarkdown), summaryMarkdown: Value(article.summaryMarkdown),
summaryPlain: Value(article.summaryPlain), summaryPlain: Value(article.summaryPlain),
tags: Value(article.tags), tags: Value(article.tags),
@@ -27,8 +27,10 @@ CREATE TABLE article (
authors TEXT MAPPED BY `const FeedAuthorsConverter()`, authors TEXT MAPPED BY `const FeedAuthorsConverter()`,
tags TEXT MAPPED BY `const FeedCategoriesConverter()`, tags TEXT MAPPED BY `const FeedCategoriesConverter()`,
links TEXT MAPPED BY `const FeedLinksConverter()`, links TEXT MAPPED BY `const FeedLinksConverter()`,
summaryHtml TEXT,
summaryMarkdown TEXT, summaryMarkdown TEXT,
summaryPlain TEXT, summaryPlain TEXT,
contentHtml TEXT,
contentMarkdown TEXT, contentMarkdown TEXT,
contentPlain TEXT contentPlain TEXT
) WITH FeedArticle; ) WITH FeedArticle;
@@ -36,7 +38,8 @@ CREATE TABLE article (
CREATE VIEW article_view WITH FeedArticle AS CREATE VIEW article_view WITH FeedArticle AS
SELECT SELECT
a.*, a.*,
f.icon f.icon,
f.site_link
FROM FROM
article a article a
INNER JOIN INNER JOIN
@@ -547,6 +547,14 @@ class Article extends Table with TableInfo<Article, FeedArticle> {
requiredDuringInsert: false, requiredDuringInsert: false,
$customConstraints: '', $customConstraints: '',
).withConverter<List<FeedLink>?>(Article.$converterlinksn); ).withConverter<List<FeedLink>?>(Article.$converterlinksn);
late final GeneratedColumn<String> summaryHtml = GeneratedColumn<String>(
'summaryHtml',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '',
);
late final GeneratedColumn<String> summaryMarkdown = GeneratedColumn<String>( late final GeneratedColumn<String> summaryMarkdown = GeneratedColumn<String>(
'summaryMarkdown', 'summaryMarkdown',
aliasedName, aliasedName,
@@ -563,6 +571,14 @@ class Article extends Table with TableInfo<Article, FeedArticle> {
requiredDuringInsert: false, requiredDuringInsert: false,
$customConstraints: '', $customConstraints: '',
); );
late final GeneratedColumn<String> contentHtml = GeneratedColumn<String>(
'contentHtml',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '',
);
late final GeneratedColumn<String> contentMarkdown = GeneratedColumn<String>( late final GeneratedColumn<String> contentMarkdown = GeneratedColumn<String>(
'contentMarkdown', 'contentMarkdown',
aliasedName, aliasedName,
@@ -591,8 +607,10 @@ class Article extends Table with TableInfo<Article, FeedArticle> {
authors, authors,
tags, tags,
links, links,
summaryHtml,
summaryMarkdown, summaryMarkdown,
summaryPlain, summaryPlain,
contentHtml,
contentMarkdown, contentMarkdown,
contentPlain, contentPlain,
]; ];
@@ -657,6 +675,10 @@ class Article extends Table with TableInfo<Article, FeedArticle> {
data['${effectivePrefix}links'], data['${effectivePrefix}links'],
), ),
), ),
summaryHtml: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}summaryHtml'],
),
summaryMarkdown: attachedDatabase.typeMapping.read( summaryMarkdown: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}summaryMarkdown'], data['${effectivePrefix}summaryMarkdown'],
@@ -665,6 +687,10 @@ class Article extends Table with TableInfo<Article, FeedArticle> {
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}summaryPlain'], data['${effectivePrefix}summaryPlain'],
), ),
contentHtml: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}contentHtml'],
),
contentMarkdown: attachedDatabase.typeMapping.read( contentMarkdown: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}contentMarkdown'], data['${effectivePrefix}contentMarkdown'],
@@ -709,8 +735,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
final Value<List<FeedAuthor>?> authors; final Value<List<FeedAuthor>?> authors;
final Value<List<FeedCategory>?> tags; final Value<List<FeedCategory>?> tags;
final Value<List<FeedLink>?> links; final Value<List<FeedLink>?> links;
final Value<String?> summaryHtml;
final Value<String?> summaryMarkdown; final Value<String?> summaryMarkdown;
final Value<String?> summaryPlain; final Value<String?> summaryPlain;
final Value<String?> contentHtml;
final Value<String?> contentMarkdown; final Value<String?> contentMarkdown;
final Value<String?> contentPlain; final Value<String?> contentPlain;
final Value<int> rowid; final Value<int> rowid;
@@ -725,8 +753,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
this.authors = const Value.absent(), this.authors = const Value.absent(),
this.tags = const Value.absent(), this.tags = const Value.absent(),
this.links = const Value.absent(), this.links = const Value.absent(),
this.summaryHtml = const Value.absent(),
this.summaryMarkdown = const Value.absent(), this.summaryMarkdown = const Value.absent(),
this.summaryPlain = const Value.absent(), this.summaryPlain = const Value.absent(),
this.contentHtml = const Value.absent(),
this.contentMarkdown = const Value.absent(), this.contentMarkdown = const Value.absent(),
this.contentPlain = const Value.absent(), this.contentPlain = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
@@ -742,8 +772,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
this.authors = const Value.absent(), this.authors = const Value.absent(),
this.tags = const Value.absent(), this.tags = const Value.absent(),
this.links = const Value.absent(), this.links = const Value.absent(),
this.summaryHtml = const Value.absent(),
this.summaryMarkdown = const Value.absent(), this.summaryMarkdown = const Value.absent(),
this.summaryPlain = const Value.absent(), this.summaryPlain = const Value.absent(),
this.contentHtml = const Value.absent(),
this.contentMarkdown = const Value.absent(), this.contentMarkdown = const Value.absent(),
this.contentPlain = const Value.absent(), this.contentPlain = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
@@ -761,8 +793,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
Expression<String>? authors, Expression<String>? authors,
Expression<String>? tags, Expression<String>? tags,
Expression<String>? links, Expression<String>? links,
Expression<String>? summaryHtml,
Expression<String>? summaryMarkdown, Expression<String>? summaryMarkdown,
Expression<String>? summaryPlain, Expression<String>? summaryPlain,
Expression<String>? contentHtml,
Expression<String>? contentMarkdown, Expression<String>? contentMarkdown,
Expression<String>? contentPlain, Expression<String>? contentPlain,
Expression<int>? rowid, Expression<int>? rowid,
@@ -778,8 +812,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
if (authors != null) 'authors': authors, if (authors != null) 'authors': authors,
if (tags != null) 'tags': tags, if (tags != null) 'tags': tags,
if (links != null) 'links': links, if (links != null) 'links': links,
if (summaryHtml != null) 'summaryHtml': summaryHtml,
if (summaryMarkdown != null) 'summaryMarkdown': summaryMarkdown, if (summaryMarkdown != null) 'summaryMarkdown': summaryMarkdown,
if (summaryPlain != null) 'summaryPlain': summaryPlain, if (summaryPlain != null) 'summaryPlain': summaryPlain,
if (contentHtml != null) 'contentHtml': contentHtml,
if (contentMarkdown != null) 'contentMarkdown': contentMarkdown, if (contentMarkdown != null) 'contentMarkdown': contentMarkdown,
if (contentPlain != null) 'contentPlain': contentPlain, if (contentPlain != null) 'contentPlain': contentPlain,
if (rowid != null) 'rowid': rowid, if (rowid != null) 'rowid': rowid,
@@ -797,8 +833,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
Value<List<FeedAuthor>?>? authors, Value<List<FeedAuthor>?>? authors,
Value<List<FeedCategory>?>? tags, Value<List<FeedCategory>?>? tags,
Value<List<FeedLink>?>? links, Value<List<FeedLink>?>? links,
Value<String?>? summaryHtml,
Value<String?>? summaryMarkdown, Value<String?>? summaryMarkdown,
Value<String?>? summaryPlain, Value<String?>? summaryPlain,
Value<String?>? contentHtml,
Value<String?>? contentMarkdown, Value<String?>? contentMarkdown,
Value<String?>? contentPlain, Value<String?>? contentPlain,
Value<int>? rowid, Value<int>? rowid,
@@ -814,8 +852,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
authors: authors ?? this.authors, authors: authors ?? this.authors,
tags: tags ?? this.tags, tags: tags ?? this.tags,
links: links ?? this.links, links: links ?? this.links,
summaryHtml: summaryHtml ?? this.summaryHtml,
summaryMarkdown: summaryMarkdown ?? this.summaryMarkdown, summaryMarkdown: summaryMarkdown ?? this.summaryMarkdown,
summaryPlain: summaryPlain ?? this.summaryPlain, summaryPlain: summaryPlain ?? this.summaryPlain,
contentHtml: contentHtml ?? this.contentHtml,
contentMarkdown: contentMarkdown ?? this.contentMarkdown, contentMarkdown: contentMarkdown ?? this.contentMarkdown,
contentPlain: contentPlain ?? this.contentPlain, contentPlain: contentPlain ?? this.contentPlain,
rowid: rowid ?? this.rowid, rowid: rowid ?? this.rowid,
@@ -861,12 +901,18 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
Article.$converterlinksn.toSql(links.value), Article.$converterlinksn.toSql(links.value),
); );
} }
if (summaryHtml.present) {
map['summaryHtml'] = Variable<String>(summaryHtml.value);
}
if (summaryMarkdown.present) { if (summaryMarkdown.present) {
map['summaryMarkdown'] = Variable<String>(summaryMarkdown.value); map['summaryMarkdown'] = Variable<String>(summaryMarkdown.value);
} }
if (summaryPlain.present) { if (summaryPlain.present) {
map['summaryPlain'] = Variable<String>(summaryPlain.value); map['summaryPlain'] = Variable<String>(summaryPlain.value);
} }
if (contentHtml.present) {
map['contentHtml'] = Variable<String>(contentHtml.value);
}
if (contentMarkdown.present) { if (contentMarkdown.present) {
map['contentMarkdown'] = Variable<String>(contentMarkdown.value); map['contentMarkdown'] = Variable<String>(contentMarkdown.value);
} }
@@ -892,8 +938,10 @@ class ArticleCompanion extends UpdateCompanion<FeedArticle> {
..write('authors: $authors, ') ..write('authors: $authors, ')
..write('tags: $tags, ') ..write('tags: $tags, ')
..write('links: $links, ') ..write('links: $links, ')
..write('summaryHtml: $summaryHtml, ')
..write('summaryMarkdown: $summaryMarkdown, ') ..write('summaryMarkdown: $summaryMarkdown, ')
..write('summaryPlain: $summaryPlain, ') ..write('summaryPlain: $summaryPlain, ')
..write('contentHtml: $contentHtml, ')
..write('contentMarkdown: $contentMarkdown, ') ..write('contentMarkdown: $contentMarkdown, ')
..write('contentPlain: $contentPlain, ') ..write('contentPlain: $contentPlain, ')
..write('rowid: $rowid') ..write('rowid: $rowid')
@@ -920,8 +968,10 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
authors, authors,
tags, tags,
links, links,
summaryHtml,
summaryMarkdown, summaryMarkdown,
summaryPlain, summaryPlain,
contentHtml,
contentMarkdown, contentMarkdown,
contentPlain, contentPlain,
icon, icon,
@@ -991,6 +1041,10 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
data['${effectivePrefix}links'], data['${effectivePrefix}links'],
), ),
), ),
summaryHtml: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}summaryHtml'],
),
summaryMarkdown: attachedDatabase.typeMapping.read( summaryMarkdown: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}summaryMarkdown'], data['${effectivePrefix}summaryMarkdown'],
@@ -999,6 +1053,10 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}summaryPlain'], data['${effectivePrefix}summaryPlain'],
), ),
contentHtml: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}contentHtml'],
),
contentMarkdown: attachedDatabase.typeMapping.read( contentMarkdown: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}contentMarkdown'], data['${effectivePrefix}contentMarkdown'],
@@ -1080,6 +1138,12 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
true, true,
type: DriftSqlType.string, type: DriftSqlType.string,
).withConverter<List<FeedLink>?>(Article.$converterlinksn); ).withConverter<List<FeedLink>?>(Article.$converterlinksn);
late final GeneratedColumn<String> summaryHtml = GeneratedColumn<String>(
'summaryHtml',
aliasedName,
true,
type: DriftSqlType.string,
);
late final GeneratedColumn<String> summaryMarkdown = GeneratedColumn<String>( late final GeneratedColumn<String> summaryMarkdown = GeneratedColumn<String>(
'summaryMarkdown', 'summaryMarkdown',
aliasedName, aliasedName,
@@ -1092,6 +1156,12 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
true, true,
type: DriftSqlType.string, type: DriftSqlType.string,
); );
late final GeneratedColumn<String> contentHtml = GeneratedColumn<String>(
'contentHtml',
aliasedName,
true,
type: DriftSqlType.string,
);
late final GeneratedColumn<String> contentMarkdown = GeneratedColumn<String>( late final GeneratedColumn<String> contentMarkdown = GeneratedColumn<String>(
'contentMarkdown', 'contentMarkdown',
aliasedName, aliasedName,
@@ -1420,8 +1490,10 @@ abstract class _$FeedDatabase extends GeneratedDatabase {
Article.$converterlinks, Article.$converterlinks,
row.readNullable<String>('links'), row.readNullable<String>('links'),
), ),
summaryHtml: row.readNullable<String>('summaryHtml'),
summaryMarkdown: row.readNullable<String>('summaryMarkdown'), summaryMarkdown: row.readNullable<String>('summaryMarkdown'),
summaryPlain: row.readNullable<String>('summaryPlain'), summaryPlain: row.readNullable<String>('summaryPlain'),
contentHtml: row.readNullable<String>('contentHtml'),
contentMarkdown: row.readNullable<String>('contentMarkdown'), contentMarkdown: row.readNullable<String>('contentMarkdown'),
contentPlain: row.readNullable<String>('contentPlain'), contentPlain: row.readNullable<String>('contentPlain'),
icon: NullAwareTypeConverter.wrapFromSql( icon: NullAwareTypeConverter.wrapFromSql(
@@ -1473,8 +1545,10 @@ abstract class _$FeedDatabase extends GeneratedDatabase {
Article.$converterlinks, Article.$converterlinks,
row.readNullable<String>('links'), row.readNullable<String>('links'),
), ),
summaryHtml: row.readNullable<String>('summaryHtml'),
summaryMarkdown: row.readNullable<String>('summaryMarkdown'), summaryMarkdown: row.readNullable<String>('summaryMarkdown'),
summaryPlain: row.readNullable<String>('summaryPlain'), summaryPlain: row.readNullable<String>('summaryPlain'),
contentHtml: row.readNullable<String>('contentHtml'),
contentMarkdown: row.readNullable<String>('contentMarkdown'), contentMarkdown: row.readNullable<String>('contentMarkdown'),
contentPlain: row.readNullable<String>('contentPlain'), contentPlain: row.readNullable<String>('contentPlain'),
icon: NullAwareTypeConverter.wrapFromSql( icon: NullAwareTypeConverter.wrapFromSql(
@@ -1911,8 +1985,10 @@ typedef $ArticleCreateCompanionBuilder =
Value<List<FeedAuthor>?> authors, Value<List<FeedAuthor>?> authors,
Value<List<FeedCategory>?> tags, Value<List<FeedCategory>?> tags,
Value<List<FeedLink>?> links, Value<List<FeedLink>?> links,
Value<String?> summaryHtml,
Value<String?> summaryMarkdown, Value<String?> summaryMarkdown,
Value<String?> summaryPlain, Value<String?> summaryPlain,
Value<String?> contentHtml,
Value<String?> contentMarkdown, Value<String?> contentMarkdown,
Value<String?> contentPlain, Value<String?> contentPlain,
Value<int> rowid, Value<int> rowid,
@@ -1929,8 +2005,10 @@ typedef $ArticleUpdateCompanionBuilder =
Value<List<FeedAuthor>?> authors, Value<List<FeedAuthor>?> authors,
Value<List<FeedCategory>?> tags, Value<List<FeedCategory>?> tags,
Value<List<FeedLink>?> links, Value<List<FeedLink>?> links,
Value<String?> summaryHtml,
Value<String?> summaryMarkdown, Value<String?> summaryMarkdown,
Value<String?> summaryPlain, Value<String?> summaryPlain,
Value<String?> contentHtml,
Value<String?> contentMarkdown, Value<String?> contentMarkdown,
Value<String?> contentPlain, Value<String?> contentPlain,
Value<int> rowid, Value<int> rowid,
@@ -2018,6 +2096,11 @@ class $ArticleFilterComposer extends Composer<_$FeedDatabase, Article> {
builder: (column) => ColumnWithTypeConverterFilters(column), builder: (column) => ColumnWithTypeConverterFilters(column),
); );
ColumnFilters<String> get summaryHtml => $composableBuilder(
column: $table.summaryHtml,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get summaryMarkdown => $composableBuilder( ColumnFilters<String> get summaryMarkdown => $composableBuilder(
column: $table.summaryMarkdown, column: $table.summaryMarkdown,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
@@ -2028,6 +2111,11 @@ class $ArticleFilterComposer extends Composer<_$FeedDatabase, Article> {
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<String> get contentHtml => $composableBuilder(
column: $table.contentHtml,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get contentMarkdown => $composableBuilder( ColumnFilters<String> get contentMarkdown => $composableBuilder(
column: $table.contentMarkdown, column: $table.contentMarkdown,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
@@ -2115,6 +2203,11 @@ class $ArticleOrderingComposer extends Composer<_$FeedDatabase, Article> {
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<String> get summaryHtml => $composableBuilder(
column: $table.summaryHtml,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get summaryMarkdown => $composableBuilder( ColumnOrderings<String> get summaryMarkdown => $composableBuilder(
column: $table.summaryMarkdown, column: $table.summaryMarkdown,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
@@ -2125,6 +2218,11 @@ class $ArticleOrderingComposer extends Composer<_$FeedDatabase, Article> {
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<String> get contentHtml => $composableBuilder(
column: $table.contentHtml,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get contentMarkdown => $composableBuilder( ColumnOrderings<String> get contentMarkdown => $composableBuilder(
column: $table.contentMarkdown, column: $table.contentMarkdown,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
@@ -2194,6 +2292,11 @@ class $ArticleAnnotationComposer extends Composer<_$FeedDatabase, Article> {
GeneratedColumnWithTypeConverter<List<FeedLink>?, String> get links => GeneratedColumnWithTypeConverter<List<FeedLink>?, String> get links =>
$composableBuilder(column: $table.links, builder: (column) => column); $composableBuilder(column: $table.links, builder: (column) => column);
GeneratedColumn<String> get summaryHtml => $composableBuilder(
column: $table.summaryHtml,
builder: (column) => column,
);
GeneratedColumn<String> get summaryMarkdown => $composableBuilder( GeneratedColumn<String> get summaryMarkdown => $composableBuilder(
column: $table.summaryMarkdown, column: $table.summaryMarkdown,
builder: (column) => column, builder: (column) => column,
@@ -2204,6 +2307,11 @@ class $ArticleAnnotationComposer extends Composer<_$FeedDatabase, Article> {
builder: (column) => column, builder: (column) => column,
); );
GeneratedColumn<String> get contentHtml => $composableBuilder(
column: $table.contentHtml,
builder: (column) => column,
);
GeneratedColumn<String> get contentMarkdown => $composableBuilder( GeneratedColumn<String> get contentMarkdown => $composableBuilder(
column: $table.contentMarkdown, column: $table.contentMarkdown,
builder: (column) => column, builder: (column) => column,
@@ -2276,8 +2384,10 @@ class $ArticleTableManager
Value<List<FeedAuthor>?> authors = const Value.absent(), Value<List<FeedAuthor>?> authors = const Value.absent(),
Value<List<FeedCategory>?> tags = const Value.absent(), Value<List<FeedCategory>?> tags = const Value.absent(),
Value<List<FeedLink>?> links = const Value.absent(), Value<List<FeedLink>?> links = const Value.absent(),
Value<String?> summaryHtml = const Value.absent(),
Value<String?> summaryMarkdown = const Value.absent(), Value<String?> summaryMarkdown = const Value.absent(),
Value<String?> summaryPlain = const Value.absent(), Value<String?> summaryPlain = const Value.absent(),
Value<String?> contentHtml = const Value.absent(),
Value<String?> contentMarkdown = const Value.absent(), Value<String?> contentMarkdown = const Value.absent(),
Value<String?> contentPlain = const Value.absent(), Value<String?> contentPlain = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
@@ -2292,8 +2402,10 @@ class $ArticleTableManager
authors: authors, authors: authors,
tags: tags, tags: tags,
links: links, links: links,
summaryHtml: summaryHtml,
summaryMarkdown: summaryMarkdown, summaryMarkdown: summaryMarkdown,
summaryPlain: summaryPlain, summaryPlain: summaryPlain,
contentHtml: contentHtml,
contentMarkdown: contentMarkdown, contentMarkdown: contentMarkdown,
contentPlain: contentPlain, contentPlain: contentPlain,
rowid: rowid, rowid: rowid,
@@ -2310,8 +2422,10 @@ class $ArticleTableManager
Value<List<FeedAuthor>?> authors = const Value.absent(), Value<List<FeedAuthor>?> authors = const Value.absent(),
Value<List<FeedCategory>?> tags = const Value.absent(), Value<List<FeedCategory>?> tags = const Value.absent(),
Value<List<FeedLink>?> links = const Value.absent(), Value<List<FeedLink>?> links = const Value.absent(),
Value<String?> summaryHtml = const Value.absent(),
Value<String?> summaryMarkdown = const Value.absent(), Value<String?> summaryMarkdown = const Value.absent(),
Value<String?> summaryPlain = const Value.absent(), Value<String?> summaryPlain = const Value.absent(),
Value<String?> contentHtml = const Value.absent(),
Value<String?> contentMarkdown = const Value.absent(), Value<String?> contentMarkdown = const Value.absent(),
Value<String?> contentPlain = const Value.absent(), Value<String?> contentPlain = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
@@ -2326,8 +2440,10 @@ class $ArticleTableManager
authors: authors, authors: authors,
tags: tags, tags: tags,
links: links, links: links,
summaryHtml: summaryHtml,
summaryMarkdown: summaryMarkdown, summaryMarkdown: summaryMarkdown,
summaryPlain: summaryPlain, summaryPlain: summaryPlain,
contentHtml: contentHtml,
contentMarkdown: contentMarkdown, contentMarkdown: contentMarkdown,
contentPlain: contentPlain, contentPlain: contentPlain,
rowid: rowid, rowid: rowid,
@@ -1,3 +1,4 @@
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
@@ -9,6 +10,7 @@ import 'package:lensai/features/web_feed/data/models/feed_link.dart';
part 'feed_article.g.dart'; part 'feed_article.g.dart';
@JsonSerializable() @JsonSerializable()
@CopyWith()
class FeedArticle with FastEquatable implements Insertable<FeedArticle> { class FeedArticle with FastEquatable implements Insertable<FeedArticle> {
final String id; final String id;
final Uri feedId; final Uri feedId;
@@ -20,13 +22,16 @@ class FeedArticle with FastEquatable implements Insertable<FeedArticle> {
final List<FeedAuthor>? authors; final List<FeedAuthor>? authors;
final List<FeedCategory>? tags; final List<FeedCategory>? tags;
final List<FeedLink>? links; final List<FeedLink>? links;
final String? summaryHtml;
final String? summaryMarkdown; final String? summaryMarkdown;
final String? summaryPlain; final String? summaryPlain;
final String? contentHtml;
final String? contentMarkdown; final String? contentMarkdown;
final String? contentPlain; final String? contentPlain;
//Derived by view from feed table, should not get inserted //Derived by view from feed table, should not get inserted
final Uri? icon; final Uri? icon;
final Uri? siteLink;
FeedArticle({ FeedArticle({
required this.id, required this.id,
@@ -39,11 +44,14 @@ class FeedArticle with FastEquatable implements Insertable<FeedArticle> {
this.authors, this.authors,
this.tags, this.tags,
this.links, this.links,
this.summaryHtml,
this.summaryMarkdown, this.summaryMarkdown,
this.summaryPlain, this.summaryPlain,
this.contentHtml,
this.contentMarkdown, this.contentMarkdown,
this.contentPlain, this.contentPlain,
this.icon, this.icon,
this.siteLink,
}); });
factory FeedArticle.fromJson(Map<String, dynamic> json) => factory FeedArticle.fromJson(Map<String, dynamic> json) =>
@@ -82,12 +90,18 @@ class FeedArticle with FastEquatable implements Insertable<FeedArticle> {
if (!nullToAbsent || links != null) { if (!nullToAbsent || links != null) {
map['links'] = Variable<String>(Article.$converterlinksn.toSql(links)); map['links'] = Variable<String>(Article.$converterlinksn.toSql(links));
} }
if (!nullToAbsent || summaryHtml != null) {
map['summaryHtml'] = Variable<String>(summaryHtml);
}
if (!nullToAbsent || summaryMarkdown != null) { if (!nullToAbsent || summaryMarkdown != null) {
map['summaryMarkdown'] = Variable<String>(summaryMarkdown); map['summaryMarkdown'] = Variable<String>(summaryMarkdown);
} }
if (!nullToAbsent || summaryPlain != null) { if (!nullToAbsent || summaryPlain != null) {
map['summaryPlain'] = Variable<String>(summaryPlain); map['summaryPlain'] = Variable<String>(summaryPlain);
} }
if (!nullToAbsent || contentHtml != null) {
map['contentHtml'] = Variable<String>(contentHtml);
}
if (!nullToAbsent || contentMarkdown != null) { if (!nullToAbsent || contentMarkdown != null) {
map['contentMarkdown'] = Variable<String>(contentMarkdown); map['contentMarkdown'] = Variable<String>(contentMarkdown);
} }
@@ -109,10 +123,13 @@ class FeedArticle with FastEquatable implements Insertable<FeedArticle> {
authors, authors,
tags, tags,
links, links,
summaryHtml,
summaryMarkdown, summaryMarkdown,
summaryPlain, summaryPlain,
contentHtml,
contentMarkdown, contentMarkdown,
contentPlain, contentPlain,
icon, icon,
siteLink,
]; ];
} }
@@ -2,6 +2,257 @@
part of 'feed_article.dart'; part of 'feed_article.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$FeedArticleCWProxy {
FeedArticle id(String id);
FeedArticle feedId(Uri feedId);
FeedArticle fetched(DateTime fetched);
FeedArticle created(DateTime? created);
FeedArticle updated(DateTime? updated);
FeedArticle lastRead(DateTime? lastRead);
FeedArticle title(String? title);
FeedArticle authors(List<FeedAuthor>? authors);
FeedArticle tags(List<FeedCategory>? tags);
FeedArticle links(List<FeedLink>? links);
FeedArticle summaryHtml(String? summaryHtml);
FeedArticle summaryMarkdown(String? summaryMarkdown);
FeedArticle summaryPlain(String? summaryPlain);
FeedArticle contentHtml(String? contentHtml);
FeedArticle contentMarkdown(String? contentMarkdown);
FeedArticle contentPlain(String? contentPlain);
FeedArticle icon(Uri? icon);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `FeedArticle(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// FeedArticle(...).copyWith(id: 12, name: "My name")
/// ````
FeedArticle call({
String id,
Uri feedId,
DateTime fetched,
DateTime? created,
DateTime? updated,
DateTime? lastRead,
String? title,
List<FeedAuthor>? authors,
List<FeedCategory>? tags,
List<FeedLink>? links,
String? summaryHtml,
String? summaryMarkdown,
String? summaryPlain,
String? contentHtml,
String? contentMarkdown,
String? contentPlain,
Uri? icon,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfFeedArticle.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfFeedArticle.copyWith.fieldName(...)`
class _$FeedArticleCWProxyImpl implements _$FeedArticleCWProxy {
const _$FeedArticleCWProxyImpl(this._value);
final FeedArticle _value;
@override
FeedArticle id(String id) => this(id: id);
@override
FeedArticle feedId(Uri feedId) => this(feedId: feedId);
@override
FeedArticle fetched(DateTime fetched) => this(fetched: fetched);
@override
FeedArticle created(DateTime? created) => this(created: created);
@override
FeedArticle updated(DateTime? updated) => this(updated: updated);
@override
FeedArticle lastRead(DateTime? lastRead) => this(lastRead: lastRead);
@override
FeedArticle title(String? title) => this(title: title);
@override
FeedArticle authors(List<FeedAuthor>? authors) => this(authors: authors);
@override
FeedArticle tags(List<FeedCategory>? tags) => this(tags: tags);
@override
FeedArticle links(List<FeedLink>? links) => this(links: links);
@override
FeedArticle summaryHtml(String? summaryHtml) =>
this(summaryHtml: summaryHtml);
@override
FeedArticle summaryMarkdown(String? summaryMarkdown) =>
this(summaryMarkdown: summaryMarkdown);
@override
FeedArticle summaryPlain(String? summaryPlain) =>
this(summaryPlain: summaryPlain);
@override
FeedArticle contentHtml(String? contentHtml) =>
this(contentHtml: contentHtml);
@override
FeedArticle contentMarkdown(String? contentMarkdown) =>
this(contentMarkdown: contentMarkdown);
@override
FeedArticle contentPlain(String? contentPlain) =>
this(contentPlain: contentPlain);
@override
FeedArticle icon(Uri? icon) => this(icon: icon);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `FeedArticle(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// FeedArticle(...).copyWith(id: 12, name: "My name")
/// ````
FeedArticle call({
Object? id = const $CopyWithPlaceholder(),
Object? feedId = const $CopyWithPlaceholder(),
Object? fetched = const $CopyWithPlaceholder(),
Object? created = const $CopyWithPlaceholder(),
Object? updated = const $CopyWithPlaceholder(),
Object? lastRead = const $CopyWithPlaceholder(),
Object? title = const $CopyWithPlaceholder(),
Object? authors = const $CopyWithPlaceholder(),
Object? tags = const $CopyWithPlaceholder(),
Object? links = const $CopyWithPlaceholder(),
Object? summaryHtml = const $CopyWithPlaceholder(),
Object? summaryMarkdown = const $CopyWithPlaceholder(),
Object? summaryPlain = const $CopyWithPlaceholder(),
Object? contentHtml = const $CopyWithPlaceholder(),
Object? contentMarkdown = const $CopyWithPlaceholder(),
Object? contentPlain = const $CopyWithPlaceholder(),
Object? icon = const $CopyWithPlaceholder(),
}) {
return FeedArticle(
id:
id == const $CopyWithPlaceholder()
? _value.id
// ignore: cast_nullable_to_non_nullable
: id as String,
feedId:
feedId == const $CopyWithPlaceholder()
? _value.feedId
// ignore: cast_nullable_to_non_nullable
: feedId as Uri,
fetched:
fetched == const $CopyWithPlaceholder()
? _value.fetched
// ignore: cast_nullable_to_non_nullable
: fetched as DateTime,
created:
created == const $CopyWithPlaceholder()
? _value.created
// ignore: cast_nullable_to_non_nullable
: created as DateTime?,
updated:
updated == const $CopyWithPlaceholder()
? _value.updated
// ignore: cast_nullable_to_non_nullable
: updated as DateTime?,
lastRead:
lastRead == const $CopyWithPlaceholder()
? _value.lastRead
// ignore: cast_nullable_to_non_nullable
: lastRead as DateTime?,
title:
title == const $CopyWithPlaceholder()
? _value.title
// ignore: cast_nullable_to_non_nullable
: title as String?,
authors:
authors == const $CopyWithPlaceholder()
? _value.authors
// ignore: cast_nullable_to_non_nullable
: authors as List<FeedAuthor>?,
tags:
tags == const $CopyWithPlaceholder()
? _value.tags
// ignore: cast_nullable_to_non_nullable
: tags as List<FeedCategory>?,
links:
links == const $CopyWithPlaceholder()
? _value.links
// ignore: cast_nullable_to_non_nullable
: links as List<FeedLink>?,
summaryHtml:
summaryHtml == const $CopyWithPlaceholder()
? _value.summaryHtml
// ignore: cast_nullable_to_non_nullable
: summaryHtml as String?,
summaryMarkdown:
summaryMarkdown == const $CopyWithPlaceholder()
? _value.summaryMarkdown
// ignore: cast_nullable_to_non_nullable
: summaryMarkdown as String?,
summaryPlain:
summaryPlain == const $CopyWithPlaceholder()
? _value.summaryPlain
// ignore: cast_nullable_to_non_nullable
: summaryPlain as String?,
contentHtml:
contentHtml == const $CopyWithPlaceholder()
? _value.contentHtml
// ignore: cast_nullable_to_non_nullable
: contentHtml as String?,
contentMarkdown:
contentMarkdown == const $CopyWithPlaceholder()
? _value.contentMarkdown
// ignore: cast_nullable_to_non_nullable
: contentMarkdown as String?,
contentPlain:
contentPlain == const $CopyWithPlaceholder()
? _value.contentPlain
// ignore: cast_nullable_to_non_nullable
: contentPlain as String?,
icon:
icon == const $CopyWithPlaceholder()
? _value.icon
// ignore: cast_nullable_to_non_nullable
: icon as Uri?,
);
}
}
extension $FeedArticleCopyWith on FeedArticle {
/// Returns a callable class that can be used as follows: `instanceOfFeedArticle.copyWith(...)` or like so:`instanceOfFeedArticle.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$FeedArticleCWProxy get copyWith => _$FeedArticleCWProxyImpl(this);
}
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
@@ -35,8 +286,10 @@ FeedArticle _$FeedArticleFromJson(Map<String, dynamic> json) => FeedArticle(
(json['links'] as List<dynamic>?) (json['links'] as List<dynamic>?)
?.map((e) => FeedLink.fromJson(e as Map<String, dynamic>)) ?.map((e) => FeedLink.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
summaryHtml: json['summaryHtml'] as String?,
summaryMarkdown: json['summaryMarkdown'] as String?, summaryMarkdown: json['summaryMarkdown'] as String?,
summaryPlain: json['summaryPlain'] as String?, summaryPlain: json['summaryPlain'] as String?,
contentHtml: json['contentHtml'] as String?,
contentMarkdown: json['contentMarkdown'] as String?, contentMarkdown: json['contentMarkdown'] as String?,
contentPlain: json['contentPlain'] as String?, contentPlain: json['contentPlain'] as String?,
icon: json['icon'] == null ? null : Uri.parse(json['icon'] as String), icon: json['icon'] == null ? null : Uri.parse(json['icon'] as String),
@@ -54,8 +307,10 @@ Map<String, dynamic> _$FeedArticleToJson(FeedArticle instance) =>
'authors': instance.authors?.map((e) => e.toJson()).toList(), 'authors': instance.authors?.map((e) => e.toJson()).toList(),
'tags': instance.tags?.map((e) => e.toJson()).toList(), 'tags': instance.tags?.map((e) => e.toJson()).toList(),
'links': instance.links?.map((e) => e.toJson()).toList(), 'links': instance.links?.map((e) => e.toJson()).toList(),
'summaryHtml': instance.summaryHtml,
'summaryMarkdown': instance.summaryMarkdown, 'summaryMarkdown': instance.summaryMarkdown,
'summaryPlain': instance.summaryPlain, 'summaryPlain': instance.summaryPlain,
'contentHtml': instance.contentHtml,
'contentMarkdown': instance.contentMarkdown, 'contentMarkdown': instance.contentMarkdown,
'contentPlain': instance.contentPlain, 'contentPlain': instance.contentPlain,
'icon': instance.icon?.toString(), 'icon': instance.icon?.toString(),
@@ -19,8 +19,10 @@ class FeedArticleQueryResult extends FeedArticle {
required super.authors, required super.authors,
required super.tags, required super.tags,
required super.links, required super.links,
required super.summaryHtml,
required super.summaryMarkdown, required super.summaryMarkdown,
required super.summaryPlain, required super.summaryPlain,
required super.contentHtml,
required super.contentMarkdown, required super.contentMarkdown,
required super.contentPlain, required super.contentPlain,
required super.icon, required super.icon,
@@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
@@ -38,8 +37,8 @@ FeedDatabase feedDatabase(Ref ref) {
}), }),
); );
ref.onDispose(() { ref.onDispose(() async {
unawaited(db.close()); await db.close();
}); });
return db; return db;
@@ -6,7 +6,7 @@ part of 'providers.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$feedDatabaseHash() => r'a4a1ffa36b73c2aecbe17bf096e30fa9b9ad23c9'; String _$feedDatabaseHash() => r'c3b20e867da5af6e92d1e8c1efa96b79988837a1';
/// See also [feedDatabase]. /// See also [feedDatabase].
@ProviderFor(feedDatabase) @ProviderFor(feedDatabase)
@@ -10,6 +10,7 @@ import 'package:lensai/features/web_feed/domain/repositories/feed_repository.dar
import 'package:lensai/features/web_feed/domain/services/feed_reader.dart'; import 'package:lensai/features/web_feed/domain/services/feed_reader.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
part 'providers.g.dart'; part 'providers.g.dart';
@@ -53,7 +54,7 @@ class ArticleSearch extends _$ArticleSearch {
await _streamController.close(); await _streamController.close();
}); });
return _streamController.stream; return ConcatStream([Stream.value([]), _streamController.stream]);
} }
} }
@@ -827,7 +827,7 @@ class _ArticleSearchProviderElement
} }
String _$filteredArticleListHash() => String _$filteredArticleListHash() =>
r'4443a4a92cab0c8f534a74a1cba681a8973b058e'; r'a691c3c6aa722dd85ee4980e6c48721a8025f1d8';
abstract class _$FilteredArticleList abstract class _$FilteredArticleList
extends BuildlessAutoDisposeNotifier<AsyncValue<List<FeedArticle>>> { extends BuildlessAutoDisposeNotifier<AsyncValue<List<FeedArticle>>> {
@@ -0,0 +1,51 @@
import 'package:collection/collection.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/web_feed/data/models/feed_article.dart';
import 'package:lensai/features/web_feed/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.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.whenNotEmpty,
contentPlain: content[index].plain.whenNotEmpty,
summaryMarkdown: summary[index].markdown.whenNotEmpty,
summaryPlain: summary[index].plain.whenNotEmpty,
),
)
.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,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'article_content_processor.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$articleContentProcessorServiceHash() =>
r'182cd89e38a0a9a86b7425c16ad2bd1fc39c6704';
/// See also [ArticleContentProcessorService].
@ProviderFor(ArticleContentProcessorService)
final articleContentProcessorServiceProvider =
NotifierProvider<ArticleContentProcessorService, void>.internal(
ArticleContentProcessorService.new,
name: r'articleContentProcessorServiceProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$articleContentProcessorServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ArticleContentProcessorService = Notifier<void>;
// 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
@@ -28,7 +28,7 @@ class FeedReader extends _$FeedReader {
final parser = FeedParser.parse(url: url, xmlString: response.body); final parser = FeedParser.parse(url: url, xmlString: response.body);
final result = FeedParseResult( final result = FeedParseResult(
feedData: parser.readGeneralData(), feedData: parser.readGeneralData(),
articleData: await parser.readArticles(), articleData: parser.readArticles(),
); );
return result.toJson(); return result.toJson();
@@ -6,7 +6,7 @@ part of 'feed_reader.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$feedReaderHash() => r'67b09485e40c522108709ca32b9c624b4db2446b'; String _$feedReaderHash() => r'301e01fa831c58723dc0ab118496f4fb34c38610';
/// See also [FeedReader]. /// See also [FeedReader].
@ProviderFor(FeedReader) @ProviderFor(FeedReader)
@@ -1,3 +1,4 @@
import 'package:lensai/core/logger.dart';
import 'package:lensai/features/web_feed/domain/repositories/feed_repository.dart'; import 'package:lensai/features/web_feed/domain/repositories/feed_repository.dart';
import 'package:lensai/features/web_feed/domain/services/feed_reader.dart'; import 'package:lensai/features/web_feed/domain/services/feed_reader.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -14,17 +15,25 @@ class FetchArticlesController extends _$FetchArticlesController {
await Future.wait( await Future.wait(
feeds.map((feed) async { feeds.map((feed) async {
final result = await ref try {
.read(feedReaderProvider.notifier) final result = await ref
.parseFeed(feed.url); .read(feedReaderProvider.notifier)
.parseFeed(feed.url);
await ref await ref
.read(feedRepositoryProvider.notifier) .read(feedRepositoryProvider.notifier)
.upsertArticles(result.articleData); .upsertArticles(result.articleData);
await ref await ref
.read(feedRepositoryProvider.notifier) .read(feedRepositoryProvider.notifier)
.touchFeedFetched(feed.url); .touchFeedFetched(feed.url);
} catch (e, s) {
logger.e(
'Failed fetching feed ${feed.url}',
error: e,
stackTrace: s,
);
}
}).toList(), }).toList(),
); );
}); });
@@ -7,7 +7,7 @@ part of 'fetch_articles.dart';
// ************************************************************************** // **************************************************************************
String _$fetchArticlesControllerHash() => String _$fetchArticlesControllerHash() =>
r'63da7f6ba9eab28aebff4c60d98e2f2a404c5138'; r'7604d9352da886624c3cf6862b89c397edda1f28';
/// See also [FetchArticlesController]. /// See also [FetchArticlesController].
@ProviderFor(FetchArticlesController) @ProviderFor(FetchArticlesController)
@@ -6,8 +6,10 @@ import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/extensions/uri.dart'; import 'package:lensai/extensions/uri.dart';
import 'package:lensai/features/web_feed/data/models/feed_article.dart'; import 'package:lensai/features/web_feed/data/models/feed_article.dart';
import 'package:lensai/features/web_feed/data/models/feed_article_query_result.dart'; import 'package:lensai/features/web_feed/data/models/feed_article_query_result.dart';
import 'package:lensai/features/web_feed/data/models/feed_link.dart';
import 'package:lensai/features/web_feed/domain/providers/article_filter.dart'; import 'package:lensai/features/web_feed/domain/providers/article_filter.dart';
import 'package:lensai/features/web_feed/domain/repositories/feed_repository.dart'; import 'package:lensai/features/web_feed/domain/repositories/feed_repository.dart';
import 'package:lensai/features/web_feed/extensions/atom.dart';
import 'package:lensai/features/web_feed/extensions/feed_article.dart'; import 'package:lensai/features/web_feed/extensions/feed_article.dart';
import 'package:lensai/features/web_feed/presentation/widgets/authors_horizontal_list.dart'; import 'package:lensai/features/web_feed/presentation/widgets/authors_horizontal_list.dart';
import 'package:lensai/features/web_feed/presentation/widgets/tags_horizontal_list.dart'; import 'package:lensai/features/web_feed/presentation/widgets/tags_horizontal_list.dart';
@@ -52,7 +54,12 @@ class FeedArticleCard extends HookConsumerWidget {
Row( Row(
children: [ children: [
UrlIcon([ UrlIcon([
article.icon ?? article.feedId.base, article.icon ??
article.links
?.getRelation(FeedLinkRelation.alternate)
?.uri ??
article.siteLink ??
article.feedId.base,
], iconSize: 34.0), ], iconSize: 34.0),
const SizedBox(width: 12.0), const SizedBox(width: 12.0),
Expanded( Expanded(
@@ -1,5 +1,4 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/extensions/nullable.dart'; import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/web_feed/data/database/database.dart'; import 'package:lensai/features/web_feed/data/database/database.dart';
import 'package:lensai/features/web_feed/data/models/feed_article.dart'; import 'package:lensai/features/web_feed/data/models/feed_article.dart';
@@ -79,28 +78,11 @@ class FeedParser {
} }
} }
Future<List<FeedArticle>> readArticles() async { List<FeedArticle> readArticles() {
final fetchDate = DateTime.now(); final fetchDate = DateTime.now();
switch (_feed) { switch (_feed) {
case final Rss1Feed feed: case final Rss1Feed feed:
final processedContents =
await GeckoBrowserExtensionService.turndownHtml(
feed.items.map((item) => item.content?.value ?? '').toList(),
);
final processedSummaries =
await GeckoBrowserExtensionService.turndownHtml(
feed.items
.map(
(item) =>
item.description.whenNotEmpty ??
item.dc?.description ??
'',
)
.toList(),
);
return feed.items.mapIndexed((i, item) { return feed.items.mapIndexed((i, item) {
final title = item.title.whenNotEmpty ?? item.dc?.title.whenNotEmpty; final title = item.title.whenNotEmpty ?? item.dc?.title.whenNotEmpty;
@@ -119,32 +101,19 @@ class FeedParser {
authors: item.dc?.creator.whenNotEmpty.mapNotNull( authors: item.dc?.creator.whenNotEmpty.mapNotNull(
(creator) => [FeedAuthor(name: creator)], (creator) => [FeedAuthor(name: creator)],
), ),
summaryPlain: processedSummaries[i].plain.whenNotEmpty, summaryHtml:
summaryMarkdown: processedSummaries[i].markdown.whenNotEmpty, item.description.whenNotEmpty ??
links: link.mapNotNull((link) => [FeedLink(uri: link)]), item.dc?.description.whenNotEmpty,
links: link.mapNotNull(
(link) => [
FeedLink(uri: link, relation: FeedLinkRelation.alternate),
],
),
tags: item.dc?.toFeedCategories(), tags: item.dc?.toFeedCategories(),
contentPlain: processedContents[i].plain.trim().whenNotEmpty, contentHtml: item.content?.value.whenNotEmpty,
contentMarkdown: processedContents[i].markdown?.trim().whenNotEmpty,
); );
}).toList(); }).toList();
case final RssFeed feed: case final RssFeed feed:
final processedContents =
await GeckoBrowserExtensionService.turndownHtml(
feed.items.map((item) => item.content?.value ?? '').toList(),
);
final processedSummaries =
await GeckoBrowserExtensionService.turndownHtml(
feed.items
.map(
(item) =>
item.description.whenNotEmpty ??
item.dc?.description ??
'',
)
.toList(),
);
return feed.items.mapIndexed((i, item) { return feed.items.mapIndexed((i, item) {
final title = item.title.whenNotEmpty ?? item.dc?.title.whenNotEmpty; final title = item.title.whenNotEmpty ?? item.dc?.title.whenNotEmpty;
@@ -173,28 +142,22 @@ class FeedParser {
authors: author.mapNotNull( authors: author.mapNotNull(
(creator) => [FeedAuthor(name: creator)], (creator) => [FeedAuthor(name: creator)],
), ),
summaryPlain: processedSummaries[i].plain.whenNotEmpty, summaryHtml:
summaryMarkdown: processedSummaries[i].markdown.whenNotEmpty, item.description.whenNotEmpty ??
links: link.mapNotNull((link) => [FeedLink(uri: link)]), item.dc?.description.whenNotEmpty,
links: link.mapNotNull(
(link) => [
FeedLink(uri: link, relation: FeedLinkRelation.alternate),
],
),
tags: tags:
categories.isNotEmpty categories.isNotEmpty
? categories ? categories
: item.dc?.toFeedCategories(), : item.dc?.toFeedCategories(),
contentPlain: processedContents[i].plain.trim().whenNotEmpty, contentHtml: item.content?.value.whenNotEmpty,
contentMarkdown: processedContents[i].markdown?.trim().whenNotEmpty,
); );
}).toList(); }).toList();
case final AtomFeed feed: case final AtomFeed feed:
final processedContents =
await GeckoBrowserExtensionService.turndownHtml(
feed.items.map((item) => item.content ?? '').toList(),
);
final processedSummaries =
await GeckoBrowserExtensionService.turndownHtml(
feed.items.map((item) => item.summary ?? '').toList(),
);
final feedLink = feed.links.toFeedLinks().getRelation( final feedLink = feed.links.toFeedLinks().getRelation(
FeedLinkRelation.self, FeedLinkRelation.self,
); );
@@ -218,16 +181,14 @@ class FeedParser {
id: uniqueId, id: uniqueId,
feedId: url, feedId: url,
title: item.title.whenNotEmpty, title: item.title.whenNotEmpty,
summaryPlain: processedSummaries[i].plain.whenNotEmpty, summaryHtml: item.summary.whenNotEmpty,
summaryMarkdown: processedSummaries[i].markdown.whenNotEmpty,
links: itemLinks, links: itemLinks,
authors: authors.isNotEmpty ? authors : null, authors: authors.isNotEmpty ? authors : null,
tags: tags, tags: tags,
fetched: fetchDate, fetched: fetchDate,
created: published, created: published,
updated: updated, updated: updated,
contentPlain: processedContents[i].plain.trim().whenNotEmpty, contentHtml: item.content.whenNotEmpty,
contentMarkdown: processedContents[i].markdown?.trim().whenNotEmpty,
); );
}).toList(); }).toList();
default: default:
@@ -0,0 +1,35 @@
import 'package:background_fetch/background_fetch.dart';
import 'package:lensai/core/error_observer.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/features/web_feed/presentation/controllers/fetch_articles.dart';
import 'package:riverpod/riverpod.dart';
@pragma('vm:entry-point')
Future<void> backgroundFetch(HeadlessTask task) async {
final taskId = task.taskId;
final isTimeout = task.timeout;
if (isTimeout) {
// This task has exceeded its allowed running-time.
// You must stop what you're doing and immediately .finish(taskId)
logger.e("[BackgroundFetch] Headless task timed-out: $taskId");
await BackgroundFetch.finish(taskId);
return;
}
final ref = ProviderContainer(observers: const [ErrorObserver()]);
try {
await ref.read(fetchArticlesControllerProvider.notifier).fetchAllArticles();
logger.i('Fetched articles in background');
} catch (e, s) {
logger.e('Failed fetching articles', error: e, stackTrace: s);
} finally {
ref.dispose();
// Give everything a bit of time to properly dispose
await Future.delayed(const Duration(seconds: 1));
await BackgroundFetch.finish(taskId);
}
}
+29 -4
View File
@@ -1,22 +1,24 @@
import 'package:background_fetch/background_fetch.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:home_widget/home_widget.dart'; import 'package:home_widget/home_widget.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/error_observer.dart'; import 'package:lensai/core/error_observer.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/core/providers/defaults.dart'; import 'package:lensai/core/providers/defaults.dart';
import 'package:lensai/domain/services/app_initialization.dart'; import 'package:lensai/domain/services/app_initialization.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart'; import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:lensai/features/web_feed/presentation/controllers/fetch_articles.dart';
import 'package:lensai/features/web_feed/utils/fetch_entrypoint.dart';
import 'package:lensai/presentation/hooks/on_initialization.dart'; import 'package:lensai/presentation/hooks/on_initialization.dart';
import 'package:lensai/presentation/main_app.dart'; import 'package:lensai/presentation/main_app.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await HomeWidget.setAppGroupId('bang_navigator'); await BackgroundFetch.registerHeadlessTask(backgroundFetch);
// if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { await HomeWidget.setAppGroupId('bang_navigator');
// await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode);
// }
runApp( runApp(
ProviderScope( ProviderScope(
@@ -33,6 +35,29 @@ void main() async {
await ref await ref
.read(appInitializationServiceProvider.notifier) .read(appInitializationServiceProvider.notifier)
.initialize(); .initialize();
await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15,
enableHeadless: true,
stopOnTerminate: false,
requiredNetworkType: NetworkType.ANY,
startOnBoot: true,
),
(String taskId) async {
try {
await ref
.read(fetchArticlesControllerProvider.notifier)
.fetchAllArticles();
logger.i('Fetched articles in foreground');
} catch (e, s) {
logger.e('Failed fetching articles', error: e, stackTrace: s);
} finally {
await BackgroundFetch.finish(taskId);
}
},
);
}); });
return DynamicColorBuilder( return DynamicColorBuilder(
+1
View File
@@ -7,6 +7,7 @@ environment:
sdk: '>=3.7.0 <4.0.0' sdk: '>=3.7.0 <4.0.0'
dependencies: dependencies:
background_fetch: ^1.3.7
collection: ^1.19.1 collection: ^1.19.1
copy_with_extension: ^6.0.1 copy_with_extension: ^6.0.1
crypto: ^3.0.6 crypto: ^3.0.6
@@ -3,6 +3,7 @@ export 'src/data/models/load_url_flags.dart';
export 'src/data/models/source.dart'; export 'src/data/models/source.dart';
export 'src/domain/entities/default_selection_actions.dart'; export 'src/domain/entities/default_selection_actions.dart';
export 'src/domain/services/gecko_addon.dart'; export 'src/domain/services/gecko_addon.dart';
export 'src/domain/services/gecko_browser_extension.dart';
export 'src/domain/services/gecko_container_proxy.dart'; export 'src/domain/services/gecko_container_proxy.dart';
export 'src/domain/services/gecko_cookie.dart'; export 'src/domain/services/gecko_cookie.dart';
export 'src/domain/services/gecko_delete_browser_data.dart'; export 'src/domain/services/gecko_delete_browser_data.dart';
@@ -18,7 +19,6 @@ export 'src/domain/services/gecko_session.dart';
export 'src/domain/services/gecko_suggestions.dart'; export 'src/domain/services/gecko_suggestions.dart';
export 'src/domain/services/gecko_tab.dart'; export 'src/domain/services/gecko_tab.dart';
export 'src/domain/services/gecko_tab_content.dart'; export 'src/domain/services/gecko_tab_content.dart';
export 'src/domain/services/gecko_browser_extension.dart';
export 'src/geckoview_widget.dart'; export 'src/geckoview_widget.dart';
export 'src/pigeons/gecko.g.dart' export 'src/pigeons/gecko.g.dart'
show show
@@ -14,9 +14,16 @@ class GeckoBrowserExtensionService extends BrowserExtensionEvents {
Stream<String> get feedRequested => _feedRequest.stream; Stream<String> get feedRequested => _feedRequest.stream;
static Future<List<TurndownResults>> turndownHtml( static Future<List<TurndownResults>> turndownHtml(
List<String> htmlList, List<String> htmlList, {
) async { Duration timeout = const Duration(seconds: 1),
final markdownResult = await _apiInstance.getMarkdown(htmlList); }) async {
if (htmlList.isEmpty) {
return [];
}
final markdownResult = await _apiInstance
.getMarkdown(htmlList)
.timeout(timeout);
final results = final results =
markdownResult markdownResult
@@ -1 +1 @@
/home/fafre/.pub-cache/hosted/pub.dev/sqlite3_flutter_libs-0.5.30/ /home/fafre/.pub-cache/hosted/pub.dev/sqlite3_flutter_libs-0.5.31/