From a47ff28ed1479ecf25f05c5f2cfe9e439da3897a Mon Sep 17 00:00:00 2001 From: Cedric Hornberger Date: Fri, 16 Oct 2026 16:03:25 +0200 Subject: [PATCH] Rename NewsSource to Source now that it models every kind (review item 3) The class and its file (lib/models/source.dart) follow the glossary term. Persisted JSON keys are unchanged. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- lib/data/default_sources.dart | 88 ++++++++++---------- lib/models/app_settings.dart | 2 +- lib/models/article.dart | 4 +- lib/models/{news_source.dart => source.dart} | 8 +- lib/services/claude_summarizer.dart | 2 +- lib/services/feed_parser.dart | 8 +- lib/services/feed_service.dart | 8 +- lib/services/n8n_summarizer.dart | 2 +- lib/services/storage.dart | 8 +- lib/services/summarizer.dart | 2 +- lib/state/app_state.dart | 20 ++--- lib/ui/article_screen.dart | 2 +- lib/ui/briefing_screen.dart | 2 +- lib/ui/feed_screen.dart | 6 +- lib/ui/learn_screen.dart | 2 +- lib/ui/settings_screen.dart | 2 +- lib/ui/sources_screen.dart | 4 +- test/article_extractor_test.dart | 4 +- test/claude_summarizer_test.dart | 4 +- test/comic_reader_test.dart | 2 +- test/consent_service_test.dart | 4 +- test/default_sources_test.dart | 4 +- test/feed_parser_test.dart | 4 +- test/fun_section_test.dart | 2 +- test/learn_tab_test.dart | 2 +- test/live_feeds_test.dart | 2 +- test/paid_newsletter_posts_test.dart | 4 +- test/paywall_service_test.dart | 4 +- test/source_kinds_test.dart | 2 +- test/source_selection_test.dart | 10 +-- test/sources_tab_test.dart | 6 +- test/support/fake_app.dart | 12 +-- 33 files changed, 119 insertions(+), 119 deletions(-) rename lib/models/{news_source.dart => source.dart} (91%) diff --git a/README.md b/README.md index f186c82..bdb211c 100644 --- a/README.md +++ b/README.md @@ -237,7 +237,7 @@ Reddit blocks reader view, so a meme shows the picture from the feed. lib/ main.dart App entry, theme data/default_sources.dart Curated feed list - models/ Article, NewsSource, DailyBriefing, AppSettings + models/ Article, Source, DailyBriefing, AppSettings services/ feed_parser.dart RSS 2.0 / RSS 1.0 / Atom parser, date parsing feed_service.dart Parallel fetching, dedupe, time window diff --git a/lib/data/default_sources.dart b/lib/data/default_sources.dart index 9864997..07731b4 100644 --- a/lib/data/default_sources.dart +++ b/lib/data/default_sources.dart @@ -1,10 +1,10 @@ -import '../models/news_source.dart'; +import '../models/source.dart'; /// Curated tech news feeds. All URLs were checked to return a valid RSS/Atom /// feed. Users can toggle them and add their own in the Sources tab. -const defaultSources = [ +const defaultSources = [ // --- German ------------------------------------------------------------- - NewsSource( + Source( id: 'heise', name: 'heise online', feedUrl: 'https://www.heise.de/rss/heise-atom.xml', @@ -13,7 +13,7 @@ const defaultSources = [ description: 'IT-Nachrichten, breit aufgestellt (hohes Volumen)', enabledByDefault: true, ), - NewsSource( + Source( id: 'golem', name: 'Golem.de', feedUrl: 'https://rss.golem.de/rss.php?feed=ATOM1.0', @@ -22,7 +22,7 @@ const defaultSources = [ description: 'IT-News für Profis', enabledByDefault: true, ), - NewsSource( + Source( id: 'the-decoder', name: 'The Decoder', feedUrl: 'https://the-decoder.de/feed/', @@ -31,7 +31,7 @@ const defaultSources = [ description: 'KI-News: Modelle, Forschung, Branche', enabledByDefault: true, ), - NewsSource( + Source( id: 't3n', name: 't3n', feedUrl: 'https://t3n.de/rss.xml', @@ -40,7 +40,7 @@ const defaultSources = [ description: 'Digitales Business, Startups, KI', checkPaywallOnPage: true, ), - NewsSource( + Source( id: 'computerbase', name: 'ComputerBase', feedUrl: 'https://www.computerbase.de/rss/news.xml', @@ -48,7 +48,7 @@ const defaultSources = [ language: SourceLanguage.de, description: 'Hardware, Software, Gaming', ), - NewsSource( + Source( id: 'spiegel-netzwelt', name: 'SPIEGEL Netzwelt', feedUrl: 'https://www.spiegel.de/netzwelt/index.rss', @@ -57,7 +57,7 @@ const defaultSources = [ description: 'Digitales aus Sicht eines Nachrichtenmagazins', checkPaywallOnPage: true, ), - NewsSource( + Source( id: 'zeit-digital', name: 'ZEIT Digital', feedUrl: 'https://newsfeed.zeit.de/digital/index', @@ -65,7 +65,7 @@ const defaultSources = [ language: SourceLanguage.de, description: 'Digitalpolitik und Gesellschaft', ), - NewsSource( + Source( id: 'netzpolitik', name: 'netzpolitik.org', feedUrl: 'https://netzpolitik.org/feed/', @@ -73,7 +73,7 @@ const defaultSources = [ language: SourceLanguage.de, description: 'Digitale Freiheitsrechte, Datenschutz', ), - NewsSource( + Source( id: 'heise-security', name: 'heise Security', feedUrl: 'https://www.heise.de/security/rss/news-atom.xml', @@ -81,7 +81,7 @@ const defaultSources = [ language: SourceLanguage.de, description: 'Security-Teil von heise online (heise online ausschalten, um ihn einzeln zu lesen)', ), - NewsSource( + Source( id: 'heise-developer', name: 'heise Developer', feedUrl: 'https://www.heise.de/developer/rss/news-atom.xml', @@ -89,7 +89,7 @@ const defaultSources = [ language: SourceLanguage.de, description: 'Developer-Teil von heise online (heise online ausschalten, um ihn einzeln zu lesen)', ), - NewsSource( + Source( id: 'caschy', name: "Caschys Blog", feedUrl: 'https://stadt-bremerhaven.de/feed/', @@ -97,7 +97,7 @@ const defaultSources = [ language: SourceLanguage.de, description: 'Consumer-Tech, Smartphones, Streaming', ), - NewsSource( + Source( id: 'derstandard-web', name: 'derStandard Web', feedUrl: 'https://www.derstandard.at/rss/web', @@ -105,7 +105,7 @@ const defaultSources = [ language: SourceLanguage.de, description: 'Tech-Ressort der österreichischen Tageszeitung', ), - NewsSource( + Source( id: 'borncity', name: 'Borns IT- und Windows-Blog', feedUrl: 'https://www.borncity.com/blog/feed/', @@ -115,7 +115,7 @@ const defaultSources = [ ), // --- English ------------------------------------------------------------ - NewsSource( + Source( id: 'verge', name: 'The Verge', feedUrl: 'https://www.theverge.com/rss/index.xml', @@ -123,7 +123,7 @@ const defaultSources = [ language: SourceLanguage.en, description: 'Consumer tech, culture, policy', ), - NewsSource( + Source( id: 'arstechnica', name: 'Ars Technica', feedUrl: 'https://feeds.arstechnica.com/arstechnica/index', @@ -132,7 +132,7 @@ const defaultSources = [ description: 'In-depth tech and science', enabledByDefault: true, ), - NewsSource( + Source( id: 'techcrunch', name: 'TechCrunch', feedUrl: 'https://techcrunch.com/feed/', @@ -140,7 +140,7 @@ const defaultSources = [ language: SourceLanguage.en, description: 'Startups, funding, big tech', ), - NewsSource( + Source( id: 'hackernews', name: 'Hacker News', feedUrl: 'https://hnrss.org/frontpage?points=150', @@ -149,7 +149,7 @@ const defaultSources = [ description: 'Front page stories with 150+ points', enabledByDefault: true, ), - NewsSource( + Source( id: 'techmeme', name: 'Techmeme', feedUrl: 'https://www.techmeme.com/feed.xml', @@ -158,7 +158,7 @@ const defaultSources = [ description: "The day's top tech stories, picked by editors", enabledByDefault: true, ), - NewsSource( + Source( id: 'lobsters', name: 'Lobsters', feedUrl: 'https://lobste.rs/rss', @@ -167,7 +167,7 @@ const defaultSources = [ description: 'Computing links, voted by a programmer community', enabledByDefault: true, ), - NewsSource( + Source( id: 'simonwillison', name: 'Simon Willison', feedUrl: 'https://simonwillison.net/atom/entries/', @@ -176,7 +176,7 @@ const defaultSources = [ description: 'LLMs and AI tools, tried out hands-on', enabledByDefault: true, ), - NewsSource( + Source( id: 'mit-tr', name: 'MIT Technology Review', feedUrl: 'https://www.technologyreview.com/feed/', @@ -184,7 +184,7 @@ const defaultSources = [ language: SourceLanguage.en, description: 'AI, biotech, climate tech', ), - NewsSource( + Source( id: 'theregister', name: 'The Register', feedUrl: 'https://www.theregister.com/headlines.atom', @@ -192,7 +192,7 @@ const defaultSources = [ language: SourceLanguage.en, description: 'Enterprise IT with attitude', ), - NewsSource( + Source( id: 'bleepingcomputer', name: 'BleepingComputer', feedUrl: 'https://www.bleepingcomputer.com/feed/', @@ -202,7 +202,7 @@ const defaultSources = [ ), // --- Learning ----------------------------------------------------------- - NewsSource( + Source( id: 'bytebytego', name: 'ByteByteGo', feedUrl: 'https://blog.bytebytego.com/feed', @@ -212,7 +212,7 @@ const defaultSources = [ description: 'System design explained with diagrams', enabledByDefault: true, ), - NewsSource( + Source( id: 'systemdesignone', name: 'System Design One', feedUrl: 'https://newsletter.systemdesign.one/feed', @@ -222,7 +222,7 @@ const defaultSources = [ description: 'System design case studies of real systems', enabledByDefault: true, ), - NewsSource( + Source( id: 'pragmaticengineer-blog', name: 'The Pragmatic Engineer (blog)', feedUrl: 'https://blog.pragmaticengineer.com/rss/', @@ -232,7 +232,7 @@ const defaultSources = [ description: 'How big tech and startups build software (free posts)', enabledByDefault: true, ), - NewsSource( + Source( id: 'martinfowler', name: 'Martin Fowler', feedUrl: 'https://martinfowler.com/feed.atom', @@ -242,7 +242,7 @@ const defaultSources = [ description: 'Software architecture, refactoring, delivery practices', enabledByDefault: true, ), - NewsSource( + Source( id: 'juliaevans', name: 'Julia Evans', feedUrl: 'https://jvns.ca/atom.xml', @@ -252,7 +252,7 @@ const defaultSources = [ description: 'Systems, networking and Unix tools, explained plainly', enabledByDefault: true, ), - NewsSource( + Source( id: 'danluu', name: 'Dan Luu', feedUrl: 'https://danluu.com/atom.xml', @@ -262,7 +262,7 @@ const defaultSources = [ description: 'Long, data-driven essays on computers and engineering', enabledByDefault: true, ), - NewsSource( + Source( id: 'cloudflare-blog', name: 'The Cloudflare Blog', feedUrl: 'https://blog.cloudflare.com/rss/', @@ -272,7 +272,7 @@ const defaultSources = [ description: 'Networking, security and incident write-ups in depth', enabledByDefault: true, ), - NewsSource( + Source( id: 'netflix-techblog', name: 'Netflix Technology Blog', feedUrl: 'https://netflixtechblog.com/feed', @@ -282,7 +282,7 @@ const defaultSources = [ description: 'Distributed systems, data and streaming at scale', enabledByDefault: true, ), - NewsSource( + Source( id: 'meta-engineering', name: 'Engineering at Meta', feedUrl: 'https://engineering.fb.com/feed/', @@ -292,7 +292,7 @@ const defaultSources = [ description: 'Infrastructure, AI and production engineering at Meta', enabledByDefault: true, ), - NewsSource( + Source( id: 'github-engineering', name: 'GitHub Blog: Engineering', feedUrl: 'https://github.blog/engineering/feed/', @@ -302,7 +302,7 @@ const defaultSources = [ description: 'How GitHub builds and runs GitHub', enabledByDefault: true, ), - NewsSource( + Source( id: 'latent-space', name: 'Latent Space', feedUrl: 'https://www.latent.space/feed', @@ -312,7 +312,7 @@ const defaultSources = [ description: 'AI engineering: interviews and deep dives', enabledByDefault: true, ), - NewsSource( + Source( id: 'import-ai', name: 'Import AI', feedUrl: 'https://importai.substack.com/feed', @@ -322,7 +322,7 @@ const defaultSources = [ description: 'Weekly AI research digest by Jack Clark', enabledByDefault: true, ), - NewsSource( + Source( id: 'interconnects', name: 'Interconnects', feedUrl: 'https://www.interconnects.ai/feed', @@ -332,7 +332,7 @@ const defaultSources = [ description: 'Language model training and research, analysed', enabledByDefault: true, ), - NewsSource( + Source( id: 'ahead-of-ai', name: 'Ahead of AI', feedUrl: 'https://magazine.sebastianraschka.com/feed', @@ -342,7 +342,7 @@ const defaultSources = [ description: 'LLM research explained, by Sebastian Raschka', enabledByDefault: true, ), - NewsSource( + Source( id: 'lilianweng', name: 'Lilian Weng', feedUrl: 'https://lilianweng.github.io/index.xml', @@ -352,7 +352,7 @@ const defaultSources = [ description: 'Thorough surveys of machine learning topics', enabledByDefault: true, ), - NewsSource( + Source( id: 'infoq', name: 'InfoQ', feedUrl: 'https://feed.infoq.com/', @@ -363,7 +363,7 @@ const defaultSources = [ ), // --- Fun ---------------------------------------------------------------- - NewsSource( + Source( id: 'xkcd', name: 'xkcd', feedUrl: 'https://xkcd.com/atom.xml', @@ -373,7 +373,7 @@ const defaultSources = [ description: 'Webcomic of romance, sarcasm, math and language', enabledByDefault: true, ), - NewsSource( + Source( id: 'thedailywtf', name: 'The Daily WTF', feedUrl: 'https://thedailywtf.com/rss', @@ -383,7 +383,7 @@ const defaultSources = [ description: 'IT horror stories, bad code and error messages', enabledByDefault: true, ), - NewsSource( + Source( id: 'programmerhumor', name: 'r/ProgrammerHumor', feedUrl: 'https://www.reddit.com/r/ProgrammerHumor/top/.rss?t=day', diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart index 42514a8..9ea9b83 100644 --- a/lib/models/app_settings.dart +++ b/lib/models/app_settings.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart' show ThemeMode; -import 'news_source.dart'; +import 'source.dart'; enum AiProvider { none('Off'), diff --git a/lib/models/article.dart b/lib/models/article.dart index 08de71b..89cd7c5 100644 --- a/lib/models/article.dart +++ b/lib/models/article.dart @@ -1,4 +1,4 @@ -import 'news_source.dart'; +import 'source.dart'; class Article { const Article({ @@ -14,7 +14,7 @@ class Article { final String title; final String link; - final NewsSource source; + final Source source; final DateTime? published; /// Short plain-text teaser from the feed (description / summary). diff --git a/lib/models/news_source.dart b/lib/models/source.dart similarity index 91% rename from lib/models/news_source.dart rename to lib/models/source.dart index d08a778..b55d28e 100644 --- a/lib/models/news_source.dart +++ b/lib/models/source.dart @@ -20,9 +20,9 @@ enum SourceKind { final String label; } -/// A Source of any [kind]; the class name predates the kinds. -class NewsSource { - const NewsSource({ +/// A Source of any [kind]; see GLOSSARY.md. +class Source { + const Source({ required this.id, required this.name, required this.feedUrl, @@ -59,7 +59,7 @@ class NewsSource { 'description': description, }; - factory NewsSource.custom(Map json) => NewsSource( + factory Source.custom(Map json) => Source( id: json['id'] as String, name: json['name'] as String, feedUrl: json['feedUrl'] as String, diff --git a/lib/services/claude_summarizer.dart b/lib/services/claude_summarizer.dart index 4a919c4..08ca805 100644 --- a/lib/services/claude_summarizer.dart +++ b/lib/services/claude_summarizer.dart @@ -5,7 +5,7 @@ import 'package:http/http.dart' as http; import '../models/article.dart'; import '../models/briefing.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import 'summarizer.dart'; /// Calls the Claude Messages API directly over HTTP (there is no official diff --git a/lib/services/feed_parser.dart b/lib/services/feed_parser.dart index 00091c5..7eac143 100644 --- a/lib/services/feed_parser.dart +++ b/lib/services/feed_parser.dart @@ -3,13 +3,13 @@ import 'package:html/parser.dart' as html_parser; import 'package:xml/xml.dart'; import '../models/article.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; /// Parses RSS 2.0, RSS 1.0 (RDF) and Atom feeds into [Article]s. class FeedParser { static const excerptLength = 400; - static List
parse(String body, NewsSource source) { + static List
parse(String body, Source source) { final doc = XmlDocument.parse(body); final root = doc.rootElement; if (root.name.local == 'feed') { @@ -27,7 +27,7 @@ class FeedParser { .toList(); } - static Article? _parseRssItem(XmlElement item, NewsSource source) { + static Article? _parseRssItem(XmlElement item, Source source) { final title = htmlToText(_text(item, 'title')); final link = _text(item, 'link').trim().isNotEmpty ? _text(item, 'link').trim() @@ -53,7 +53,7 @@ class FeedParser { ); } - static Article? _parseAtomEntry(XmlElement entry, NewsSource source) { + static Article? _parseAtomEntry(XmlElement entry, Source source) { final title = htmlToText(_text(entry, 'title')); final links = entry.childElements.where((e) => e.name.local == 'link'); final link = links diff --git a/lib/services/feed_service.dart b/lib/services/feed_service.dart index d73ac1b..5dce82e 100644 --- a/lib/services/feed_service.dart +++ b/lib/services/feed_service.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../models/article.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import 'feed_parser.dart'; class FeedResult { @@ -30,8 +30,8 @@ class FeedService { /// [corsProxy] is only used on web, where browsers block cross-origin feed /// requests. It is prepended to the feed URL, e.g. `https://proxy/?url=`. Future fetchAll( - List sources, { - required Duration Function(NewsSource source) maxAgeOf, + List sources, { + required Duration Function(Source source) maxAgeOf, String corsProxy = '', }) async { final errors = {}; @@ -64,7 +64,7 @@ class FeedService { return FeedResult(articles, errors); } - Future> fetchSource(NewsSource source, {String corsProxy = ''}) async { + Future> fetchSource(Source source, {String corsProxy = ''}) async { final url = kIsWeb && corsProxy.isNotEmpty ? '$corsProxy${Uri.encodeComponent(source.feedUrl)}' : source.feedUrl; diff --git a/lib/services/n8n_summarizer.dart b/lib/services/n8n_summarizer.dart index 983629b..a5ebf6f 100644 --- a/lib/services/n8n_summarizer.dart +++ b/lib/services/n8n_summarizer.dart @@ -4,7 +4,7 @@ import 'package:http/http.dart' as http; import '../models/article.dart'; import '../models/briefing.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import 'summarizer.dart'; /// Delegates summarizing to an existing n8n workflow via a Webhook trigger. diff --git a/lib/services/storage.dart b/lib/services/storage.dart index a4f0155..319df07 100644 --- a/lib/services/storage.dart +++ b/lib/services/storage.dart @@ -9,7 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../models/app_settings.dart'; import '../models/briefing.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; /// Persists settings, source selection and the last briefing. /// The Claude API key goes to the platform keystore, everything else to @@ -81,15 +81,15 @@ class Storage { Future saveSeenSourceIds(Set ids) => _prefs.setStringList('seen_sources', ids.toList()); - List loadCustomSources() { + List loadCustomSources() { final raw = _prefs.getString('custom_sources'); if (raw == null) return []; return (jsonDecode(raw) as List) - .map((j) => NewsSource.custom(j as Map)) + .map((j) => Source.custom(j as Map)) .toList(); } - Future saveCustomSources(List sources) => _prefs.setString( + Future saveCustomSources(List sources) => _prefs.setString( 'custom_sources', jsonEncode(sources.map((s) => s.toJson()).toList())); /// Article URL -> whether its page said it's paid, and when that was checked. diff --git a/lib/services/summarizer.dart b/lib/services/summarizer.dart index 2a0760a..efc484c 100644 --- a/lib/services/summarizer.dart +++ b/lib/services/summarizer.dart @@ -1,6 +1,6 @@ import '../models/article.dart'; import '../models/briefing.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; class SummarizerException implements Exception { SummarizerException(this.message); diff --git a/lib/state/app_state.dart b/lib/state/app_state.dart index ec4bcbc..305888e 100644 --- a/lib/state/app_state.dart +++ b/lib/state/app_state.dart @@ -6,7 +6,7 @@ import '../data/default_sources.dart'; import '../models/app_settings.dart'; import '../models/article.dart'; import '../models/briefing.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import '../services/article_extractor.dart'; import '../services/claude_summarizer.dart'; import '../services/consent_service.dart'; @@ -33,7 +33,7 @@ class AppState extends ChangeNotifier { this._storage, { FeedService? feedService, ArticleExtractor? extractor, - List curatedSources = defaultSources, + List curatedSources = defaultSources, Summarizer? summarizer, }) : _feeds = feedService ?? FeedService(), _curated = curatedSources, @@ -47,7 +47,7 @@ class AppState extends ChangeNotifier { final FeedService _feeds; /// The curated catalog; [defaultSources] outside of tests. - final List _curated; + final List _curated; /// Used instead of the AI set up in [settings]; for tests. final Summarizer? _summarizerOverride; @@ -58,7 +58,7 @@ class AppState extends ChangeNotifier { final ConsentService consent; AppSettings settings = const AppSettings(); - List customSources = []; + List customSources = []; Set enabledSourceIds = {}; /// Everything the feeds returned; see [articlesOf] for what is shown. @@ -99,9 +99,9 @@ class AppState extends ChangeNotifier { SourceKind.fun => const Duration(days: 3), }; - List get allSources => [..._curated, ...customSources]; + List get allSources => [..._curated, ...customSources]; - List get enabledSources => + List get enabledSources => allSources.where((s) => enabledSourceIds.contains(s.id)).toList(); Future init() async { @@ -156,7 +156,7 @@ class AppState extends ChangeNotifier { await _storage.saveEnabledSourceIds(enabledSourceIds); } - Future setSourceEnabled(NewsSource source, bool enabled) async { + Future setSourceEnabled(Source source, bool enabled) async { enabled ? enabledSourceIds.add(source.id) : enabledSourceIds.remove(source.id); notifyListeners(); await _storage.saveEnabledSourceIds(enabledSourceIds); @@ -171,7 +171,7 @@ class AppState extends ChangeNotifier { SourceKind kind = SourceKind.news, }) async { final uri = Uri.tryParse(feedUrl); - final source = NewsSource( + final source = Source( id: 'custom-${DateTime.now().millisecondsSinceEpoch}', name: name, feedUrl: feedUrl, @@ -194,7 +194,7 @@ class AppState extends ChangeNotifier { return null; } - Future removeCustomSource(NewsSource source) async { + Future removeCustomSource(Source source) async { customSources = customSources.where((s) => s.id != source.id).toList(); enabledSourceIds.remove(source.id); await _storage.saveCustomSources(customSources); @@ -235,7 +235,7 @@ class AppState extends ChangeNotifier { } final source = allSources.firstWhere( (s) => s.name == sourceName, - orElse: () => NewsSource( + orElse: () => Source( id: sourceName, name: sourceName, feedUrl: '', diff --git a/lib/ui/article_screen.dart b/lib/ui/article_screen.dart index d5da1ca..3bb2079 100644 --- a/lib/ui/article_screen.dart +++ b/lib/ui/article_screen.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import '../models/article.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import '../services/article_extractor.dart'; import '../services/consent_service.dart'; import '../services/content_blocking.dart'; diff --git a/lib/ui/briefing_screen.dart b/lib/ui/briefing_screen.dart index 36c1c2b..b8a69d6 100644 --- a/lib/ui/briefing_screen.dart +++ b/lib/ui/briefing_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import '../models/briefing.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import '../state/app_state.dart'; import 'article_screen.dart'; import 'briefing_history_screen.dart'; diff --git a/lib/ui/feed_screen.dart b/lib/ui/feed_screen.dart index cf2c307..e0aeca5 100644 --- a/lib/ui/feed_screen.dart +++ b/lib/ui/feed_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import '../models/article.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import '../state/app_state.dart'; import 'article_screen.dart'; import 'common.dart'; @@ -113,7 +113,7 @@ class _FeedScreenState extends State { ); } - Widget _filterBar(List sources) { + Widget _filterBar(List sources) { sources.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); return SizedBox( height: 52, @@ -157,7 +157,7 @@ class _FeedScreenState extends State { ); } - static const _placeholder = NewsSource( + static const _placeholder = Source( id: '', name: 'All sources', feedUrl: '', homepage: '', language: SourceLanguage.en); } diff --git a/lib/ui/learn_screen.dart b/lib/ui/learn_screen.dart index 1b607b2..e834bf7 100644 --- a/lib/ui/learn_screen.dart +++ b/lib/ui/learn_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import '../state/app_state.dart'; import 'article_screen.dart'; import 'feed_screen.dart'; diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index ba1be46..72e82e8 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../models/app_settings.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import '../state/app_state.dart'; import 'common.dart'; diff --git a/lib/ui/sources_screen.dart b/lib/ui/sources_screen.dart index 3c3fea8..42bc160 100644 --- a/lib/ui/sources_screen.dart +++ b/lib/ui/sources_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../models/news_source.dart'; +import '../models/source.dart'; import '../state/app_state.dart'; class SourcesScreen extends StatelessWidget { @@ -109,7 +109,7 @@ class _SourceTile extends StatelessWidget { this.onDelete, }); - final NewsSource source; + final Source source; final bool enabled; final ValueChanged onToggle; final VoidCallback? onDelete; diff --git a/test/article_extractor_test.dart b/test/article_extractor_test.dart index 8b9b5c7..fafc16c 100644 --- a/test/article_extractor_test.dart +++ b/test/article_extractor_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:daily_tech_news/models/article.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/article_extractor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; @@ -88,7 +88,7 @@ void main() { title: 'Story', link: 'https://www.golem.de/news/story.html', excerpt: 'Teaser', - source: NewsSource( + source: Source( id: 'golem', name: 'Golem.de', feedUrl: '', homepage: '', language: SourceLanguage.de), ); final content = await extractor.load(article); diff --git a/test/claude_summarizer_test.dart b/test/claude_summarizer_test.dart index 8fe8055..10e6eb8 100644 --- a/test/claude_summarizer_test.dart +++ b/test/claude_summarizer_test.dart @@ -1,14 +1,14 @@ import 'dart:convert'; import 'package:daily_tech_news/models/article.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/claude_summarizer.dart'; import 'package:daily_tech_news/services/summarizer.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; -const _source = NewsSource( +const _source = Source( id: 'verge', name: 'The Verge', feedUrl: 'https://example.com/feed', diff --git a/test/comic_reader_test.dart b/test/comic_reader_test.dart index 81e59be..ab0dc32 100644 --- a/test/comic_reader_test.dart +++ b/test/comic_reader_test.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:daily_tech_news/models/article.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/article_extractor.dart'; import 'package:daily_tech_news/ui/article_screen.dart'; import 'package:flutter/material.dart'; diff --git a/test/consent_service_test.dart b/test/consent_service_test.dart index edca0e7..861d94c 100644 --- a/test/consent_service_test.dart +++ b/test/consent_service_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:daily_tech_news/models/article.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/article_extractor.dart'; import 'package:daily_tech_news/services/consent_service.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -50,7 +50,7 @@ const _article = Article( title: 'Story', link: _articleUrl, excerpt: 'Teaser', - source: NewsSource( + source: Source( id: 'golem', name: 'Golem.de', feedUrl: '', homepage: '', language: SourceLanguage.de), ); diff --git a/test/default_sources_test.dart b/test/default_sources_test.dart index 7d55215..262b64b 100644 --- a/test/default_sources_test.dart +++ b/test/default_sources_test.dart @@ -1,8 +1,8 @@ import 'package:daily_tech_news/data/default_sources.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:flutter_test/flutter_test.dart'; -NewsSource _byId(String id) => defaultSources.singleWhere((s) => s.id == id); +Source _byId(String id) => defaultSources.singleWhere((s) => s.id == id); void main() { test('the News sources on by default are the curated quality set', () { diff --git a/test/feed_parser_test.dart b/test/feed_parser_test.dart index ac06f01..10e5c26 100644 --- a/test/feed_parser_test.dart +++ b/test/feed_parser_test.dart @@ -1,8 +1,8 @@ -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/feed_parser.dart'; import 'package:flutter_test/flutter_test.dart'; -const _source = NewsSource( +const _source = Source( id: 'test', name: 'Test', feedUrl: 'https://example.com/feed', diff --git a/test/fun_section_test.dart b/test/fun_section_test.dart index e9841a7..27096df 100644 --- a/test/fun_section_test.dart +++ b/test/fun_section_test.dart @@ -1,7 +1,7 @@ import 'package:daily_tech_news/data/default_sources.dart'; import 'package:daily_tech_news/models/article.dart'; import 'package:daily_tech_news/models/briefing.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/summarizer.dart'; import 'package:daily_tech_news/state/app_state.dart'; import 'package:daily_tech_news/ui/feed_screen.dart'; diff --git a/test/learn_tab_test.dart b/test/learn_tab_test.dart index fd0e8d6..0a78b90 100644 --- a/test/learn_tab_test.dart +++ b/test/learn_tab_test.dart @@ -1,4 +1,4 @@ -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/state/app_state.dart'; import 'package:daily_tech_news/ui/feed_screen.dart'; import 'package:daily_tech_news/ui/home_shell.dart'; diff --git a/test/live_feeds_test.dart b/test/live_feeds_test.dart index 70ffe28..4f9aae0 100644 --- a/test/live_feeds_test.dart +++ b/test/live_feeds_test.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:daily_tech_news/data/default_sources.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/article_extractor.dart'; import 'package:daily_tech_news/services/consent_service.dart'; import 'package:daily_tech_news/services/feed_service.dart'; diff --git a/test/paid_newsletter_posts_test.dart b/test/paid_newsletter_posts_test.dart index 665580d..66c5e55 100644 --- a/test/paid_newsletter_posts_test.dart +++ b/test/paid_newsletter_posts_test.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:daily_tech_news/models/article.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/feed_parser.dart'; import 'package:daily_tech_news/services/feed_service.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -17,7 +17,7 @@ class _SavedFeeds extends FeedService { final Map fixtureByFeedUrl; @override - Future> fetchSource(NewsSource source, {String corsProxy = ''}) async { + Future> fetchSource(Source source, {String corsProxy = ''}) async { final xml = File(fixtureByFeedUrl[source.feedUrl]!).readAsStringSync(); final anHourAgo = DateTime.now().toUtc().subtract(const Duration(hours: 1)); return [ diff --git a/test/paywall_service_test.dart b/test/paywall_service_test.dart index 0d2ba19..2f3ecf9 100644 --- a/test/paywall_service_test.dart +++ b/test/paywall_service_test.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:daily_tech_news/models/article.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/feed_parser.dart'; import 'package:daily_tech_news/services/paywall_service.dart'; import 'package:daily_tech_news/services/storage.dart'; @@ -10,7 +10,7 @@ import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; -NewsSource _source({bool checkPage = false}) => NewsSource( +Source _source({bool checkPage = false}) => Source( id: 'src', name: 'Source', feedUrl: 'https://example.com/feed', diff --git a/test/source_kinds_test.dart b/test/source_kinds_test.dart index 066064d..bb49562 100644 --- a/test/source_kinds_test.dart +++ b/test/source_kinds_test.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:daily_tech_news/data/default_sources.dart'; import 'package:daily_tech_news/models/article.dart'; import 'package:daily_tech_news/models/briefing.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/summarizer.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/test/source_selection_test.dart b/test/source_selection_test.dart index 50e6069..8578bd0 100644 --- a/test/source_selection_test.dart +++ b/test/source_selection_test.dart @@ -1,5 +1,5 @@ import 'package:daily_tech_news/data/default_sources.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/feed_service.dart'; import 'package:daily_tech_news/services/storage.dart'; import 'package:daily_tech_news/state/app_state.dart'; @@ -9,12 +9,12 @@ import 'package:shared_preferences/shared_preferences.dart'; /// Returns no articles, so starting the app doesn't hit the network. class _NoFeeds extends FeedService { @override - Future fetchAll(List sources, - {required Duration Function(NewsSource source) maxAgeOf, String corsProxy = ''}) async => + Future fetchAll(List sources, + {required Duration Function(Source source) maxAgeOf, String corsProxy = ''}) async => const FeedResult([], {}); } -NewsSource _source(String id, {bool onByDefault = false}) => NewsSource( +Source _source(String id, {bool onByDefault = false}) => Source( id: id, name: id, feedUrl: 'https://example.com/$id.xml', @@ -31,7 +31,7 @@ const _learningOnByDefault = { }; /// Starts the app the way a launch does, against whatever is in preferences. -Future _launch(List curatedSources) async { +Future _launch(List curatedSources) async { final state = AppState(await Storage.open(), feedService: _NoFeeds(), curatedSources: curatedSources); await state.init(); diff --git a/test/sources_tab_test.dart b/test/sources_tab_test.dart index 41670ed..67dbf36 100644 --- a/test/sources_tab_test.dart +++ b/test/sources_tab_test.dart @@ -1,4 +1,4 @@ -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/state/app_state.dart'; import 'package:daily_tech_news/ui/home_shell.dart'; import 'package:daily_tech_news/ui/learn_screen.dart'; @@ -9,7 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'support/fake_app.dart'; -NewsSource _source(String id, SourceKind kind, SourceLanguage language) => NewsSource( +Source _source(String id, SourceKind kind, SourceLanguage language) => Source( id: id, name: id, feedUrl: 'https://example.com/$id.xml', @@ -30,7 +30,7 @@ void main() { setUp(() => SharedPreferences.setMockInitialValues({})); - Future launch(WidgetTester tester, List sources) async => + Future launch(WidgetTester tester, List sources) async => (await tester.runAsync(() => launchApp(sources)))!; Future showSources(WidgetTester tester, AppState state) async { diff --git a/test/support/fake_app.dart b/test/support/fake_app.dart index 60b7769..4b659f3 100644 --- a/test/support/fake_app.dart +++ b/test/support/fake_app.dart @@ -1,5 +1,5 @@ import 'package:daily_tech_news/models/article.dart'; -import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/models/source.dart'; import 'package:daily_tech_news/services/article_extractor.dart'; import 'package:daily_tech_news/services/feed_service.dart'; import 'package:daily_tech_news/services/storage.dart'; @@ -18,14 +18,14 @@ class FakeFeeds extends FeedService { int refreshes = 0; @override - Future fetchAll(List sources, - {required Duration Function(NewsSource source) maxAgeOf, String corsProxy = ''}) { + Future fetchAll(List sources, + {required Duration Function(Source source) maxAgeOf, String corsProxy = ''}) { refreshes++; return super.fetchAll(sources, maxAgeOf: maxAgeOf, corsProxy: corsProxy); } @override - Future> fetchSource(NewsSource source, {String corsProxy = ''}) async { + Future> fetchSource(Source source, {String corsProxy = ''}) async { final now = DateTime.now().toUtc(); // A feed nobody set up still has an article, so it can be added. if (!byFeedUrl.containsKey(source.feedUrl)) { @@ -43,7 +43,7 @@ class FakeFeeds extends FeedService { } } -NewsSource testSource(String id, SourceKind kind) => NewsSource( +Source testSource(String id, SourceKind kind) => Source( id: id, name: id, feedUrl: 'https://example.com/$id.xml', @@ -55,7 +55,7 @@ NewsSource testSource(String id, SourceKind kind) => NewsSource( /// Starts the app the way a launch does, against whatever is in preferences. Future launchApp( - List curatedSources, { + List curatedSources, { Map> feeds = const {}, FeedService? feedService, Summarizer? summarizer,