good state

This commit is contained in:
Fabian Freund
2025-03-07 20:35:35 +01:00
parent 70d1c1b50e
commit 25c9faa3ff
50 changed files with 993 additions and 439 deletions
@@ -975,6 +975,7 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
contentMarkdown,
contentPlain,
icon,
siteLink,
];
@override
String get aliasedName => _alias ?? entityName;
@@ -983,7 +984,7 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
@override
Map<SqlDialect, String> get createViewStatements => {
SqlDialect.sqlite:
'CREATE VIEW article_view AS SELECT a.*, f.icon FROM article AS a INNER JOIN feed AS f ON f.url = a.feed_id',
'CREATE VIEW article_view AS SELECT a.*, f.icon, f.site_link FROM article AS a INNER JOIN feed AS f ON f.url = a.feed_id',
};
@override
ArticleView get asDslTable => this;
@@ -1071,6 +1072,12 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
data['${effectivePrefix}icon'],
),
),
siteLink: Feed.$convertersiteLinkn.fromSql(
attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}site_link'],
),
),
);
}
@@ -1181,6 +1188,13 @@ class ArticleView extends ViewInfo<ArticleView, FeedArticle>
true,
type: DriftSqlType.string,
).withConverter<Uri?>(Feed.$convertericonn);
late final GeneratedColumnWithTypeConverter<Uri?, String> siteLink =
GeneratedColumn<String>(
'site_link',
aliasedName,
true,
type: DriftSqlType.string,
).withConverter<Uri?>(Feed.$convertersiteLinkn);
@override
ArticleView createAlias(String alias) {
return ArticleView(attachedDatabase, alias);
@@ -41,6 +41,8 @@ abstract class _$FeedArticleCWProxy {
FeedArticle icon(Uri? icon);
FeedArticle siteLink(Uri? siteLink);
/// 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
@@ -65,6 +67,7 @@ abstract class _$FeedArticleCWProxy {
String? contentMarkdown,
String? contentPlain,
Uri? icon,
Uri? siteLink,
});
}
@@ -131,6 +134,9 @@ class _$FeedArticleCWProxyImpl implements _$FeedArticleCWProxy {
@override
FeedArticle icon(Uri? icon) => this(icon: icon);
@override
FeedArticle siteLink(Uri? siteLink) => this(siteLink: siteLink);
@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.
///
@@ -156,6 +162,7 @@ class _$FeedArticleCWProxyImpl implements _$FeedArticleCWProxy {
Object? contentMarkdown = const $CopyWithPlaceholder(),
Object? contentPlain = const $CopyWithPlaceholder(),
Object? icon = const $CopyWithPlaceholder(),
Object? siteLink = const $CopyWithPlaceholder(),
}) {
return FeedArticle(
id:
@@ -243,6 +250,11 @@ class _$FeedArticleCWProxyImpl implements _$FeedArticleCWProxy {
? _value.icon
// ignore: cast_nullable_to_non_nullable
: icon as Uri?,
siteLink:
siteLink == const $CopyWithPlaceholder()
? _value.siteLink
// ignore: cast_nullable_to_non_nullable
: siteLink as Uri?,
);
}
}
@@ -293,6 +305,8 @@ FeedArticle _$FeedArticleFromJson(Map<String, dynamic> json) => FeedArticle(
contentMarkdown: json['contentMarkdown'] as String?,
contentPlain: json['contentPlain'] as String?,
icon: json['icon'] == null ? null : Uri.parse(json['icon'] as String),
siteLink:
json['siteLink'] == null ? null : Uri.parse(json['siteLink'] as String),
);
Map<String, dynamic> _$FeedArticleToJson(FeedArticle instance) =>
@@ -314,4 +328,5 @@ Map<String, dynamic> _$FeedArticleToJson(FeedArticle instance) =>
'contentMarkdown': instance.contentMarkdown,
'contentPlain': instance.contentPlain,
'icon': instance.icon?.toString(),
'siteLink': instance.siteLink?.toString(),
};
@@ -684,7 +684,7 @@ class _FetchWebFeedProviderElement
Uri get url => (origin as FetchWebFeedProvider).url;
}
String _$articleSearchHash() => r'8bf2c4aa8d8b3918be8a416909535682abfbab35';
String _$articleSearchHash() => r'cc59a5d3c4b926db9f03ed96db592965ff925b04';
abstract class _$ArticleSearch
extends BuildlessAutoDisposeStreamNotifier<List<FeedArticle>> {
@@ -0,0 +1,43 @@
import 'package:lensai/core/logger.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:riverpod_annotation/riverpod_annotation.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,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'add_dialog_blocking.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$addFeedDialogBlockingHash() =>
r'513071d5e507dd292df6b4a3ee3ef7d2217db5bb';
/// See also [AddFeedDialogBlocking].
@ProviderFor(AddFeedDialogBlocking)
final addFeedDialogBlockingProvider =
NotifierProvider<AddFeedDialogBlocking, void>.internal(
AddFeedDialogBlocking.new,
name: r'addFeedDialogBlockingProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$addFeedDialogBlockingHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AddFeedDialogBlocking = 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
@@ -1,7 +1,6 @@
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';
@@ -29,10 +28,10 @@ class ArticleContentProcessorService extends _$ArticleContentProcessorService {
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,
contentMarkdown: content[index].markdown ?? '',
contentPlain: content[index].plain,
summaryMarkdown: summary[index].markdown ?? '',
summaryPlain: summary[index].plain,
),
)
.toList(),
@@ -7,7 +7,7 @@ part of 'article_content_processor.dart';
// **************************************************************************
String _$articleContentProcessorServiceHash() =>
r'182cd89e38a0a9a86b7425c16ad2bd1fc39c6704';
r'e7cc3da71f6dcf39b0c10df4dcd061f816d5c12e';
/// See also [ArticleContentProcessorService].
@ProviderFor(ArticleContentProcessorService)
@@ -16,8 +16,9 @@ extension ParseRssCategory on List<RssCategory> {
List<FeedCategory> toFeedCategories() {
return where((category) => category.value.isNotEmpty)
.map(
(category) =>
FeedCategory(id: '${category.domain} ${category.value}'.trim()),
(category) => FeedCategory(
id: '${category.domain ?? ''} ${category.value ?? ''}'.trim(),
),
)
.toList();
}
@@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/web_feed/domain/providers/add_dialog_blocking.dart';
import 'package:lensai/utils/form_validators.dart';
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
@@ -38,6 +39,16 @@ class AddFeedDialog extends HookConsumerWidget {
),
),
actions: [
if (initialUri != null)
TextButton(
onPressed: () {
ref
.read(addFeedDialogBlockingProvider.notifier)
.ignore(initialUri!);
context.pop();
},
child: const Text('Ignore'),
),
TextButton(
onPressed: () {
context.pop();