From f12fb1dabb1d8f921c64e73ba59d80ddde7efd43 Mon Sep 17 00:00:00 2001 From: Cedric Hornberger Date: Fri, 16 Oct 2026 14:53:37 +0200 Subject: [PATCH] Initial commit: Tagesbyte prototype Flutter app that collects daily tech news from German and English RSS/Atom feeds, with a reader view, AI article summaries and daily briefings (Claude API or n8n webhook), paywall/sponsored filtering and ad/consent blocking. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 65 ++ .metadata | 39 + AGENTS.md | 13 + README.md | 197 +++++ analysis_options.yaml | 28 + android/.gitignore | 14 + android/app/build.gradle.kts | 44 ++ android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 53 ++ .../daily_tech_news/MainActivity.kt | 5 + .../drawable-hdpi/ic_launcher_background.png | Bin 0 -> 4679 bytes .../drawable-hdpi/ic_launcher_foreground.png | Bin 0 -> 3833 bytes .../drawable-hdpi/ic_launcher_monochrome.png | Bin 0 -> 2766 bytes .../drawable-mdpi/ic_launcher_background.png | Bin 0 -> 1274 bytes .../drawable-mdpi/ic_launcher_foreground.png | Bin 0 -> 2139 bytes .../drawable-mdpi/ic_launcher_monochrome.png | Bin 0 -> 1761 bytes .../res/drawable-v21/launch_background.xml | 12 + .../drawable-xhdpi/ic_launcher_background.png | Bin 0 -> 6435 bytes .../drawable-xhdpi/ic_launcher_foreground.png | Bin 0 -> 4987 bytes .../drawable-xhdpi/ic_launcher_monochrome.png | Bin 0 -> 3187 bytes .../ic_launcher_background.png | Bin 0 -> 12194 bytes .../ic_launcher_foreground.png | Bin 0 -> 8653 bytes .../ic_launcher_monochrome.png | Bin 0 -> 4536 bytes .../ic_launcher_background.png | Bin 0 -> 27207 bytes .../ic_launcher_foreground.png | Bin 0 -> 12909 bytes .../ic_launcher_monochrome.png | Bin 0 -> 6034 bytes .../main/res/drawable/launch_background.xml | 12 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 14 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3044 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1371 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3426 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7973 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 8795 bytes .../app/src/main/res/values-night/styles.xml | 18 + android/app/src/main/res/values/styles.xml | 18 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle.kts | 21 + android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle.kts | 25 + assets/icon/background.png | Bin 0 -> 219895 bytes assets/icon/background.svg | 8 + assets/icon/foreground.png | Bin 0 -> 41337 bytes assets/icon/foreground.svg | 25 + assets/icon/icon.png | Bin 0 -> 225995 bytes assets/icon/icon.svg | 32 + assets/icon/make_icons.js | 123 ++++ assets/icon/monochrome.png | Bin 0 -> 15573 bytes assets/icon/monochrome.svg | 21 + docs/agents/domain.md | 51 ++ docs/agents/issue-tracker.md | 40 + docs/agents/triage-labels.md | 15 + instructions.md | 13 + ios/.gitignore | 34 + ios/Flutter/AppFrameworkInfo.plist | 26 + ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Runner.xcodeproj/project.pbxproj | 616 ++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 1 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 65400 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 511 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1060 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1645 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 732 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1676 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 2740 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1060 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 2508 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 4089 bytes .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin 0 -> 1336 bytes .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin 0 -> 3298 bytes .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin 0 -> 1565 bytes .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin 0 -> 3505 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 4089 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 7590 bytes .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin 0 -> 2485 bytes .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin 0 -> 6921 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 2548 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 8321 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 7691 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + ios/Runner/Base.lproj/LaunchScreen.storyboard | 37 + ios/Runner/Base.lproj/Main.storyboard | 26 + ios/Runner/Info.plist | 47 ++ ios/Runner/Runner-Bridging-Header.h | 1 + ios/RunnerTests/RunnerTests.swift | 12 + lib/data/default_sources.dart | 187 +++++ lib/main.dart | 45 ++ lib/models/app_settings.dart | 102 +++ lib/models/article.dart | 30 + lib/models/briefing.dart | 81 +++ lib/models/news_source.dart | 55 ++ lib/services/article_extractor.dart | 250 +++++++ lib/services/claude_summarizer.dart | 175 +++++ lib/services/consent_service.dart | 104 +++ lib/services/content_blocking.dart | 136 ++++ lib/services/feed_parser.dart | 198 +++++ lib/services/feed_service.dart | 98 +++ lib/services/n8n_summarizer.dart | 91 +++ lib/services/paywall_service.dart | 105 +++ lib/services/storage.dart | 165 +++++ lib/services/summarizer.dart | 88 +++ lib/services/web_dark_mode.dart | 26 + lib/state/app_state.dart | 267 +++++++ lib/ui/article_screen.dart | 546 ++++++++++++++ lib/ui/briefing_history_screen.dart | 191 +++++ lib/ui/briefing_screen.dart | 211 ++++++ lib/ui/common.dart | 86 +++ lib/ui/feed_screen.dart | 322 +++++++++ lib/ui/home_shell.dart | 75 ++ lib/ui/settings_screen.dart | 271 +++++++ lib/ui/sources_screen.dart | 223 ++++++ pubspec.lock | 682 ++++++++++++++++++ pubspec.yaml | 123 ++++ test/article_extractor_test.dart | 124 ++++ test/briefing_history_test.dart | 78 ++ test/claude_summarizer_test.dart | 119 +++ test/consent_service_test.dart | 101 +++ test/content_blocking_test.dart | 46 ++ test/feed_parser_test.dart | 98 +++ test/live_feeds_test.dart | 56 ++ test/paywall_service_test.dart | 85 +++ web/favicon.png | Bin 0 -> 428 bytes web/icons/Icon-192.png | Bin 0 -> 8795 bytes web/icons/Icon-512.png | Bin 0 -> 28170 bytes web/icons/Icon-maskable-192.png | Bin 0 -> 8795 bytes web/icons/Icon-maskable-512.png | Bin 0 -> 28170 bytes web/index.html | 38 + web/manifest.json | 35 + windows/.gitignore | 17 + windows/CMakeLists.txt | 108 +++ windows/flutter/CMakeLists.txt | 109 +++ .../flutter/generated_plugin_registrant.cc | 20 + windows/flutter/generated_plugin_registrant.h | 15 + windows/flutter/generated_plugins.cmake | 26 + windows/runner/CMakeLists.txt | 40 + windows/runner/Runner.rc | 121 ++++ windows/runner/flutter_window.cpp | 71 ++ windows/runner/flutter_window.h | 33 + windows/runner/main.cpp | 43 ++ windows/runner/resource.h | 16 + windows/runner/resources/app_icon.ico | Bin 0 -> 10692 bytes windows/runner/runner.exe.manifest | 14 + windows/runner/utils.cpp | 65 ++ windows/runner/utils.h | 19 + windows/runner/win32_window.cpp | 288 ++++++++ windows/runner/win32_window.h | 102 +++ 159 files changed, 8738 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/de/dailytechnews/daily_tech_news/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-hdpi/ic_launcher_background.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_launcher_background.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle.kts create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle.kts create mode 100644 assets/icon/background.png create mode 100644 assets/icon/background.svg create mode 100644 assets/icon/foreground.png create mode 100644 assets/icon/foreground.svg create mode 100644 assets/icon/icon.png create mode 100644 assets/icon/icon.svg create mode 100644 assets/icon/make_icons.js create mode 100644 assets/icon/monochrome.png create mode 100644 assets/icon/monochrome.svg create mode 100644 docs/agents/domain.md create mode 100644 docs/agents/issue-tracker.md create mode 100644 docs/agents/triage-labels.md create mode 100644 instructions.md create mode 100644 ios/.gitignore create mode 100644 ios/Flutter/AppFrameworkInfo.plist create mode 100644 ios/Flutter/Debug.xcconfig create mode 100644 ios/Flutter/Release.xcconfig create mode 100644 ios/Runner.xcodeproj/project.pbxproj create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner/AppDelegate.swift create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 ios/Runner/Base.lproj/Main.storyboard create mode 100644 ios/Runner/Info.plist create mode 100644 ios/Runner/Runner-Bridging-Header.h create mode 100644 ios/RunnerTests/RunnerTests.swift create mode 100644 lib/data/default_sources.dart create mode 100644 lib/main.dart create mode 100644 lib/models/app_settings.dart create mode 100644 lib/models/article.dart create mode 100644 lib/models/briefing.dart create mode 100644 lib/models/news_source.dart create mode 100644 lib/services/article_extractor.dart create mode 100644 lib/services/claude_summarizer.dart create mode 100644 lib/services/consent_service.dart create mode 100644 lib/services/content_blocking.dart create mode 100644 lib/services/feed_parser.dart create mode 100644 lib/services/feed_service.dart create mode 100644 lib/services/n8n_summarizer.dart create mode 100644 lib/services/paywall_service.dart create mode 100644 lib/services/storage.dart create mode 100644 lib/services/summarizer.dart create mode 100644 lib/services/web_dark_mode.dart create mode 100644 lib/state/app_state.dart create mode 100644 lib/ui/article_screen.dart create mode 100644 lib/ui/briefing_history_screen.dart create mode 100644 lib/ui/briefing_screen.dart create mode 100644 lib/ui/common.dart create mode 100644 lib/ui/feed_screen.dart create mode 100644 lib/ui/home_shell.dart create mode 100644 lib/ui/settings_screen.dart create mode 100644 lib/ui/sources_screen.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/article_extractor_test.dart create mode 100644 test/briefing_history_test.dart create mode 100644 test/claude_summarizer_test.dart create mode 100644 test/consent_service_test.dart create mode 100644 test/content_blocking_test.dart create mode 100644 test/feed_parser_test.dart create mode 100644 test/live_feeds_test.dart create mode 100644 test/paywall_service_test.dart create mode 100644 web/favicon.png create mode 100644 web/icons/Icon-192.png create mode 100644 web/icons/Icon-512.png create mode 100644 web/icons/Icon-maskable-192.png create mode 100644 web/icons/Icon-maskable-512.png create mode 100644 web/index.html create mode 100644 web/manifest.json create mode 100644 windows/.gitignore create mode 100644 windows/CMakeLists.txt create mode 100644 windows/flutter/CMakeLists.txt create mode 100644 windows/flutter/generated_plugin_registrant.cc create mode 100644 windows/flutter/generated_plugin_registrant.h create mode 100644 windows/flutter/generated_plugins.cmake create mode 100644 windows/runner/CMakeLists.txt create mode 100644 windows/runner/Runner.rc create mode 100644 windows/runner/flutter_window.cpp create mode 100644 windows/runner/flutter_window.h create mode 100644 windows/runner/main.cpp create mode 100644 windows/runner/resource.h create mode 100644 windows/runner/resources/app_icon.ico create mode 100644 windows/runner/runner.exe.manifest create mode 100644 windows/runner/utils.cpp create mode 100644 windows/runner/utils.h create mode 100644 windows/runner/win32_window.cpp create mode 100644 windows/runner/win32_window.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73181c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Windows +Thumbs.db +desktop.ini + +# Machine-specific editor settings (absolute paths) +.vscode/settings.json + +# Test coverage +coverage/ + +# Secrets and signing +.env +.env.* +*.keystore +*.jks +key.properties + +# Flutter version manager +.fvm/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..916bcca --- /dev/null +++ b/.metadata @@ -0,0 +1,39 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "edada7c56edf4a183c1735310e123c7f923584f1" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: android + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: ios + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: web + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + - platform: windows + create_revision: edada7c56edf4a183c1735310e123c7f923584f1 + base_revision: edada7c56edf4a183c1735310e123c7f923584f1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f18257d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +## Agent skills + +### Issue tracker + +Issues live in Gitea at tea.zendric.de (Cedric/TagesByte), managed with the `tea` CLI. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Default vocabulary: needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix. See `docs/agents/triage-labels.md`. + +### Domain docs + +Single-context: one `GLOSSARY.md` and `docs/adr/` at the repo root. See `docs/agents/domain.md`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..12765c8 --- /dev/null +++ b/README.md @@ -0,0 +1,197 @@ +# Tagesbyte + +Your daily tech news in bytes. A Flutter app that collects the day's tech news +from German and English RSS/Atom feeds and uses AI to summarize single articles +or write a daily briefing. It's meant to replace the n8n → Discord workflow. + +## Features (prototype) + +- **Today**: all articles from the enabled sources in the last 12/24/48 h, newest + first. Filter by language or source, pull to refresh. +- **Reading in the app**: tapping an article opens a clean **reader view** (text, + headings, lists, quotes, images) without ads or cookie banners. The globe icon + switches to the **original page** in an in-app WebView, for paywalls or sites + that block reader view. **Summarize with AI** uses the same downloaded text, so + the summary is based on the full article, not just the RSS teaser. Links in the + briefing open the same way. +- **Briefing**: AI picks the 6–10 most important stories of the day, merges + coverage from different outlets, and links every topic to its sources. Every + briefing is saved on the device (`briefings.json` in the app's private + storage); the history button lists them by month, tap to read, swipe to delete + (with undo). +- **Paid articles and ads hidden**: heise+ and Golem `(g+)` are recognized by + their title; SPIEGEL+ and t3n Pro by a marker in the article page, which the app + checks once per new article in the background (it reads only until the marker, + and remembers results for 14 days). Sponsored posts (`Anzeige:`) are hidden too. + The feed header shows how many were hidden; both filters can be turned off. +- **Ad and cookie-banner blocking** in the original-page view: requests to ~80 + ad/tracker hosts and the common consent managers (Sourcepoint, OneTrust, + consentmanager, Usercentrics, Didomi, Cookiebot, …) are blocked, and a + stylesheet hides leftover ad slots and banners. Nothing is accepted, so sites + treat it like a rejection. Golem redirects to a separate consent page instead + of showing a banner; with "Accept cookie walls" on (default), the app makes the + same request as Golem's own "Zustimmen" button (`/abo/setconsentcookie.php`, + valid for a day) and reuses the cookie for reader view and the in-app page. + With it off, the app shows the teaser and an "Open website anyway" option. +- **Sources**: 21 curated feeds (12 German, 9 English), toggle each one, or add any + RSS/Atom feed (it's validated before being added). +- **Dark mode**: System / Light / Dark in Settings (applies instantly). The + original-page view follows it too: when the phone is dark, sites use their own + dark design or Android darkens them; when only the app is dark, the app inverts + the page colours and keeps photos and videos as they are. +- **Settings**: AI provider (off / Claude API / n8n webhook), model, summary + language (German/English, whatever the source language), time window, paid/ad + filters, ad and cookie-banner blocking. + +## Running + +```bash +flutter pub get +flutter run # pick an Android emulator/device +flutter test # unit tests (parser, Claude request/response handling) +LIVE_FEEDS=1 flutter test test/live_feeds_test.dart # checks every real feed +``` + +Platform notes: + +- **Android / iOS**: work as-is (Android 6.0+). +- **Windows desktop**: requires Visual Studio with the "Desktop development with + C++" workload. Reader view works; "original page" opens the browser because + the WebView plugin has no Windows support. +- **Web**: browsers block cross-origin feed requests (CORS). Set a CORS proxy + prefix in Settings (only shown on web), or use the app on mobile/desktop. + +## AI setup + +### Option A: Claude API (direct) + +1. Create a key at . +2. Settings → AI summaries → **Claude API** → paste the key → Save. + +The key is stored in the platform keystore (`flutter_secure_storage`) and only +sent to `api.anthropic.com`. Model choices: + +| Model | Use it for | +|---|---| +| `claude-opus-5-5` (default) | Best briefing quality | +| `claude-sonnet-5-5` | Balanced cost and quality | +| `claude-haiku-5-5` | Cheapest; good enough for single-article summaries | + +Requests use low effort (summarizing is routine work) and, for the briefing, +structured JSON output so the result maps reliably to topics and links. On Opus +and Sonnet the app also enables Anthropic's server-side refusal fallback +(`fallbacks: "default"`): if a safety classifier declines a request, the API +retries it on a fallback model. + +> Shipping the app to other people with your key inside isn't safe. For +> personal use a key you entered yourself is fine; for distribution, route +> requests through your own backend (e.g. the n8n option below). + +### Option B: Your n8n workflow + +Settings → AI summaries → **n8n webhook** → enter the production URL of a +*Webhook* trigger node (method POST, "Respond: Using 'Respond to Webhook' node"). +The app sends JSON and expects JSON back. + +**Daily briefing** + +```jsonc +// Request +{ + "action": "daily_briefing", + "language": "de", // "de" | "en" + "articles": [ + { + "id": "a1", + "title": "…", + "url": "https://…", + "source": "heise online", + "language": "de", + "published": "2026-10-11T07:30:00.000Z", + "excerpt": "…" + } + ] +} + +// Response, option 1 (structured; topics link back to the articles) +{ + "intro": "One or two sentences on the day.", + "topics": [ + { "title": "…", "summary": "…", "article_ids": ["a1", "a7"] } + ] +} + +// Response, option 2 (plain text, e.g. your existing Discord message) +{ "text": "…" } +``` + +**Single article** + +```jsonc +// Request +{ + "action": "summarize_article", + "language": "de", + "article": { "title": "…", "url": "…", "source": "…", "published": "…", "text": "extracted article body" } +} + +// Response +{ "summary": "…" } // a plain-text body also works +``` + +Use a *Switch* node on `{{$json.body.action}}` to route the two cases to your +existing AI nodes. + +## News sources + +Every feed URL below was checked to load and parse. The ★ sources are enabled by +default. + +| | Source | Feed | Focus | +|---|---|---|---| +| 🇩🇪 ★ | heise online | `heise.de/rss/heise-atom.xml` | Broad IT news, high volume (~150/day) | +| 🇩🇪 ★ | Golem.de | `rss.golem.de/rss.php?feed=ATOM1.0` | IT news for professionals | +| 🇩🇪 ★ | t3n | `t3n.de/rss.xml` | Digital business, startups, AI | +| 🇩🇪 ★ | ComputerBase | `computerbase.de/rss/news.xml` | Hardware, software, gaming | +| 🇩🇪 | SPIEGEL Netzwelt | `spiegel.de/netzwelt/index.rss` | Mainstream news magazine view | +| 🇩🇪 | ZEIT Digital | `newsfeed.zeit.de/digital/index` | Digital policy and society | +| 🇩🇪 | netzpolitik.org | `netzpolitik.org/feed/` | Digital rights, privacy | +| 🇩🇪 | heise Security | `heise.de/security/rss/news-atom.xml` | Security vulnerabilities | +| 🇩🇪 | heise Developer | `heise.de/developer/rss/news-atom.xml` | Software development | +| 🇩🇪 | Caschys Blog | `stadt-bremerhaven.de/feed/` | Consumer tech, smartphones | +| 🇩🇪 | derStandard Web | `derstandard.at/rss/web` | Austrian daily's tech section | +| 🇩🇪 | Borns IT- und Windows-Blog | `borncity.com/blog/feed/` | Windows, patches, admin topics | +| 🇬🇧 ★ | The Verge | `theverge.com/rss/index.xml` | Consumer tech, policy | +| 🇬🇧 ★ | Ars Technica | `feeds.arstechnica.com/arstechnica/index` | In-depth tech and science | +| 🇬🇧 ★ | TechCrunch | `techcrunch.com/feed/` | Startups, funding | +| 🇬🇧 ★ | Hacker News | `news.ycombinator.com/rss` | Front page links | +| 🇬🇧 | MIT Technology Review | `technologyreview.com/feed/` | AI, biotech, climate | +| 🇬🇧 | WIRED | `wired.com/feed/rss` | Tech and culture | +| 🇬🇧 | Engadget | `engadget.com/rss.xml` | Gadgets | +| 🇬🇧 | The Register | `theregister.com/headlines.atom` | Enterprise IT | +| 🇬🇧 | BleepingComputer | `bleepingcomputer.com/feed/` | Security, malware | + +Reader view works for 20 of the 21 sources. ZEIT Digital blocks non-browser +requests, so the app shows the teaser and offers the original page. + +## Project layout + +``` +lib/ + main.dart App entry, theme + data/default_sources.dart Curated feed list + models/ Article, NewsSource, DailyBriefing, AppSettings + services/ + feed_parser.dart RSS 2.0 / RSS 1.0 / Atom parser, date parsing + feed_service.dart Parallel fetching, dedupe, time window + article_extractor.dart Article page → reader blocks + text for the AI + summarizer.dart Summarizer interface and shared prompt input + claude_summarizer.dart Claude Messages API over HTTP + n8n_summarizer.dart n8n webhook bridge + storage.dart Settings, sources, briefing persistence + paywall_service.dart Paid/sponsored detection (titles + page marker) + content_blocking.dart Ad/tracker/consent blocklists for the WebView + consent_service.dart Accepts consent walls (Golem) and keeps the cookies + state/app_state.dart ChangeNotifier holding app state + ui/ Screens (Today, Article, Briefing, Sources, Settings) +``` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..1ee244c --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "de.dailytechnews.daily_tech_news" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "de.dailytechnews.daily_tech_news" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 23 // flutter_secure_storage needs Android 6.0+ + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8669ffd --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/de/dailytechnews/daily_tech_news/MainActivity.kt b/android/app/src/main/kotlin/de/dailytechnews/daily_tech_news/MainActivity.kt new file mode 100644 index 0000000..2e52779 --- /dev/null +++ b/android/app/src/main/kotlin/de/dailytechnews/daily_tech_news/MainActivity.kt @@ -0,0 +1,5 @@ +package de.dailytechnews.daily_tech_news + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png new file mode 100644 index 0000000000000000000000000000000000000000..765d93acee124ae40001dc717204b3daa7803037 GIT binary patch literal 4679 zcmYLNc{o)2{}*BiDPiK+O0*DjBgZH%vX!l}r6^6=Cd?!z*~*AC(@|H;*b;LqWy`h3 zWEjh7K~2_Dm^o-cj6DXwh=_=+gZ-gn z=reEa!bqTZ&Xkg>h{$F+heMWbm$SyR*ZoycEoWn+NXv>9EXm4BH3 zEGmVdeI!3tru6P__Ug;N_wkkL>MPH34*1q^S9!=s)x~=*Spy~C@{Brpyn{g)yv9`H z!h;sWo7BiV^=CY>E`$Qfssqyj-xV+Y>QfxL?Nj7D7cldBUwjgA?zx3Y@hi#f>Bm&V zn0^Bc^Tq~oKi2nESNRnnd{vW<#Bay^I6Et26%&3(lfH6bOXAVC=7blfmDI^^Iq9l# zHBM} zC5?Oe-Oa~bo~rrIo+sSWMTJXxkrRswG5xQl@SY9wkH@O=37fhXrpX*}Zu!I7jQvHq zMp3c1%(EV)TIK|CudT$Cm&AqV{4T-Ji;}#hIKvSyUpJ>KeLf=Zjj`HeiM_Pt>DG78 zEf?fv;>qs64mpdNMzHFBZa{omAJSVk_+s#^QpJQ9=s4nP9SZSymRRz~Rxxw(q!gr_ zaCtzPHeS3atQ>!+1As|%B%3;Ulj{rW7@0TNnE!lex_!*7JIgS0TW z&O4A=FV2AeM!_VK+bdCd>z*9wJABvjHJRRv)-aPo0wQjZ$3|c7 zxNQXP2Pty2oZfF4ac;lUsl*5;5uwBou|Vd7v{?MM?2p|1QEgZ)@l z^@5fP&p~W7*JRH&z^v_XFwo)iTLefGPOY|sI;Iza+7nS%i<)re+9hV?PDzBjgo{9O zGW#M>7PC^Q3gzg9iGy;M_ABEl$j5;n#inG|ts*vw72wIQt|<87-mZUbXCv;b4OEXC z@cT)oGw$-ReZydjWUtHlS1E)Tdvj~~3~~69vZ*MHO%(c432|o(I5>l_Ca^`*jgN)# zx{$*$=5}&nBkt!TWBxPEziD0jFoHIl1jF7)Jq$C0w%#v%Mw)ZkVMWf0B7G>PTy#{m zszxF@Yj}xRX2HgtNM^l~=sTJDswPMNT$GuT7d6&>Dth^X{ghc>?c2Oauhn~zC8HmL z2(qUZ4Kz9rZ(Kh?EA2`@ST*1HJ2Z)y^g^y^JZwz&ly?26M+h-(KsMp_Q1CzFuHDGK ze=6lf_xpDWtmOCOKjfTO^)g#daCj{H^b@*U)uz*1Ai=WZVPi0|W`hU9Q0JBtURq8q zi2zlp^;=Lj)L8eO14CVG)-qLo^?cD1##XCl;CyV^3!-&7Y86MF&==wb9CG_iQKH_(w@0| zrUEptZdkJ1^k(7@VEeBhrf0kJN|i|m_WzC*hZ^IownEoj728sfCPAf-Q%NY;Qdf}_ z*_5o)7ur?aggfIW0wIlX|>9|}XoNHWy|Ta=$^S&st(fcQKm z+4jWU0Q2jq#2-J5GQrSt9q7c<>`7FIA}N8C=2GM|cR84Q8<2wb?Ed=G6c2<~X3L>E zv8Zpp2_niDZ%%;fK!3NNWVAFji{m^{+>gmrhWz+Nef6PK#IXP5U~kT+W@K3hV78|x z?jgmjdK>lbZ77X@q}1k-5E}+Cv(~f>ne-_HV7(lRT6ABr0J8{>7A1^s>k1;ps6qox zcKpk5aXu0Y^zb+ng@yXd&&@D#L*r%*r!Egl5Dtbh3saN~LGEP`$*j*8J9}>x+F>y$30x||ek3K85p;F-8fa-zDj9vM|ZzjY@t*L>wgA9$L8)*Y} z=#%@MM8C+NFb+eI0#b~?&Ztc=!N>NE$jc=DRcI65JVcqK%D{tSS`gP`FGm|~NbaqA z!3S?6Ellodi+^|ieWeP}sE0qgN#~C>Kr12Giem|}-Fbozu)mh+vlL6zR}ud5x=sM> zzUGE(0|@fAY9{$Mjg<1;z`osG;6u)o?RDg0UWZROA0#^l7{7g9Gk8atOvjH>+I9oe zVi!7)USf2oR=q&-FSO`ha?)`gHblR#{yx(J3iG9nh`pJx4(7V88eA+Oeb^9I04qgkbiH?6!1VxM#-YqzrsOY!)s}Ajg`E($K7D2v ziB(~}WLps!b9rhB156tq=T$Fu#J1$$)n)f#fYc%zg#`Kbqv!jgm}td~#tOXNGcisH zu@pd(wi!-A{IzjwDsrF^-Vbd$>2f~yguY6vnFnvdvz`Cm+xp~nB(o=PYxWxNfWHRL zgSPQj#)gXj59db~WT7fPL=zG)ew=k6(J4jNBq56E`#fyvV-Aj4`q?XDro@KuZh>z+ zmO?1MM>9lm(gBWW%KU@pF{p+=KXm5tez;KyGXaIUY7H-_MZE>tZx!Iu;Y^aSwb*cl zq}D!&U3QR~Gm!MF2H`BFu2XuL%lHy*FY`-g=@ z77fk2!z*5k{(bK3>k-rM@RiG^$6W*TRcfzH)ta(%B4a)hquKhSu8awex3chu-YDH7 zSA~%r@7>v%ssQU(o;|`}XjRE%^E-6esr9AE*HRT&Peb@&6D+*pPt>a!qA)~j5Nt?( zDjH_$qVzKaZziq)$(lxrkY5rkzI3C=7pz;@o-tw%q;hbE9k_3e{ut>T!@u3IRA?C?P>85xXXb>LV_dUHVQA=^Z?^n5`)F zKhRx71?JnOtVSG^qOd0PK|r#sOw}_RF;AIGGtIcb5%Hxa!5+}IHp4ds(FI$&)1BXW z8R;e~yk|1v4u>Vg@)tIs zxohA<(CM3m&ITLCKWj7Xt26?iBoB#^zC=V#i^Dr5BLit9$BT{-LB>kxcpZ^XyalPF zj$OVEj0=kjM~&gVmuw*`X=H*U*cEf=qypS#pSYF)v!B%C-rPZYlTnDF%a7PiIu$ed zb7}-d(-mvq=?Ro}Uy0mFH8d8nSX+t~dqK%?X9s+8KU6mNz-0v1x@Xa;0pJow5E$c&F2-~CLq zXwpBwM~XA@T`^Ihl0r3HoOYF8Y^F{QYD9c6O(Vtr7Z0KZTi*N|59T)LvOQ0*xNiK$id3VC|T z6`w9Tt2Om#v&#l%-b@t8)lqsUb(@@}IA=`6{W@_1&zKa1#=rc2?*`wcy+FygC_Rog z2lsaCRYd9DVPJZDMp5ZZTcvfj;rdI>M;Y#zrG_~|x!T;ee zP%bs_9B6BLyXVwu4k&lS2{=NcivCNZW`VxO(BG}f2sH2FSr9tAGbt@ zY@uAJJiprJ@n)ErHgLK}z>ni}S%?BXk;nia1zp%L-7P%ETgElkp;1eKp;S4J;dz}$M+BPmoy0oPhwe_S{SEmggUVf zgh3^VK#ePQ>;Fv#kV`Uw@kDnoC$S|?AIceImR5*CYl1YshkOAR{?>s?dw{);6ky<$ zlI+)eC`#XDt^?(EZT$lv#D9)Ws|YO45kmj8FDXQfA>f6H2Rv-v*@WOTDDBA1irD}= z07*e(Nok=08Xi2mCii3k!6E zxn5m5jpRGjzIs=(Cy>Lj`;ZBa2uvc=CE(l#(r}>is&APH95?N4v`58m2bph^y@@=yZxYs>mz!z03DWRC==$#h;p(!^{2N{qED*v9qETb+23*>K5-ZG+wK&-@tDTIIenEY z2iCRZ7Wyw&T!JH}LV~#;08gbqvr^wY+_RrS#34R`3Ml!+a(G2 zQbwMj!z;n&D2awZxo4ugfz{T;QR3oF12pwk<+xr)CU4lL66DY**<)++ntue)g_!NO z!~lS4VQu_k1@!--Obk6vOM0OG2YQ&%6AR`}`jkn)a^Fx3W7w7{$H12ooY-+WVY%E2OL*`mi z+D_1tmNc3KlS35MtAze~%|>!M@5s0+!^33oE|?TPKOP(r+IKz$k*)QY-2SZ{vh6%ST^@8Eylv{HdQ|sBjonT&l`v!T0Nk?!wjZqwq zsi6bM)=%1_-KiQ2w(VXIpFgDj@8RHdWoYh4SO@htTtFWR1q(k*~f_^X#*i5kSQjJb&xDmHnDw8LtEmFfn;Aw1MY2lw%IxTj8R>7i6?4a zPg#Ub(5?pcMpr7KDN`M061j<*=T5vL39oq@4T_d}DdEmQG3pa*la7xba|`|8=`F|h14nKf9|^aum*{I=RN9OLuSP^uaIK_uc}<9w*t I>P*7_0Tc;6P5=M^ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..1bdb7ebb84c87fc00aa56a95ac2751ba477b7e2b GIT binary patch literal 3833 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 IX5bR}4==Gcd;kCd literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png new file mode 100644 index 0000000000000000000000000000000000000000..8c287834fb06ddb806dfa132bdcbed5b1039b4c5 GIT binary patch literal 1274 zcmV z;j*GI3`X-K54q2=??2leulIwr4V6)WqI%clXauW+8r&W-na2ev5}j6Y`iuf8*h!U#w(`8w!UJ@Y3j4*AJ(@q z9;5o~`CpFrb6ekoWyfPjF*f;Xa3+Lh`+phM<4(QvM52jgBV3p@nBF)MIOYo+HX61nO_*0sd2iDUtnw17t`FRcN4tA2G zSs`EvKIx!YUVZUk$L%JKy!&3fOg6Jb3Kqgg4;LYuk=+vEw$HeY5I4%6Ym zgcZgt#8aA;!z@J5In5g7lhZ83(mBog6jnU5a3ZY^P7iBdvk*boWU|Aur<>0#jEDAv zlNHwB4Mscoo3KjVf7$;8mgV#Ttlyi?Vb(X^4ITmOduE}7(ySb2VNYKclNHuvW+5LP z7DHHJ%v#>o6IKqha8tJ!!U|^=;w_UQ2rIT(>u)*;D+Vmve^6ijSt4PDH4Eo#e$zo% z?s&NMoog0Ot6g%071Asm)y#4B4koM!X5n7vgf+@10IWC6LJVE^XMrVyd;-9-{YUjV zu!{Wxa}HSZnuUGs56%i}ZnMxZnB$xYR+En%+);gr%)&#?9OsSX!oqUQiUiB{AJw-5 zRBZgS%{}yF==4k%Pi!hIbq$;EIhD&mY4?CJ-?}bxoKd{VHP?%>(^hg5W~!ICalP2p|$x<$IW5c{-gRXf+g;{^@qV~ifNO(8dh+# zaHr>)RSichShoMDzKdarY+VDBX7%+2fK`fj4NRKV*B1nqbl1Q<(ky%ODjv<+U!VOf z@o2Lm!Lt2F^@W1G+5GI1CwTjh9%WCFlknJ zSf%V)x1MH&fhF>4-Fli83YHAlt*2SxV2QmPRT@~ff0fVdumrDRiD2E>EQHfEux$S- zpV?tam5;gyx5-@%i)Jn7?B|EI$z2W0_OJ4p9hSKHXlPh8tIKC&j&lH5wttn+?67>Z zuq@hb kPk#+ecW`Ld)n*}||IGzod46w-cK`qY07*qoM6N<$f})aui~s-t literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..d46fa967e76d596655592a5988031b5d6e879fe2 GIT binary patch literal 2139 zcmbtW`#Tc~1Kv29>kh^&gvw=@T+&2tjg>nk@WXN z+&>T(`@z{^K0E+G{DhzqheTL?Na;=%snab-NY**(+39c-lia zkI~bp=jC)`%l|F%n=Q>olUjFP5sUK797$#t)OZIyG>xdPadlA8L(4_fX8}ah=lDxF zBP6pMHqRzaPfXP>tyM(ib4tnV)1z;OE#9}{iE8WXVM|4fz+;m2BMG1ixU`mwNrBNT z53Z)2ME?J8sOij+eXX<2MWk1m!Q98#*q0HkOC5=y5Ua>O5_VkqwX) zVPK##u4cXerP8A=F|OXZq})u2nkdS5pxzNhIGy~x&1l?Z>LR6ZcLHy-t#5Y2cz-cb zw&&v!gEp!+NDy1af5`FW2mU0!@&MU5-4g0NdT^_7w~*xX)Q;WZ0CLd?2YQ@A*mO1k zy%I@#r7Dw%r&zD4=PY_goSi6R4_MmnE4)1mZzu);MPSd(YzHS1MY1S@<-rJKg8e;N zXJ(XQ0&;M;^!lu1q%AUvXgT&VS=HV88)qKhnedfOCg(zI^?`2s7`8SA7i)i}#gOqw zT=q7HdNX-@CUwt(J9`Dzj-u+$R>vqT@wT?oD3?ziIM~@mZ{?n}*=cXv+*(()xN&$) z&_+F4S-qk5s~qQebQf*v5sQEpbZkHWFvW`6pdKch6ciF%uVVk5K9$FGgxIW>^^FL= z)YOUtk?cJt`y+VFn=A}!s{8DtZn?7x%C`J6ikFrShMmQV`lJ_VKWSlH>tf-2rJbmd za?OqTvZMPrA3^MNTVkB6XV7$66=}Qk`)wjI)ZNrV9@Nyfn#;4k931mLMnSYPT_#(R z=uMtUTA`;H8g6@c?yiLF5!VmLv(%IM+uh8N)P5apXiaLj-}lea@$GE(vO){z>5_y2 zF64oF|FHINgw95lFDh$CL^_M#yxCK~*{sqv(Y9Qwlt=;%18SpYOw6z-DH4`sB|JzJ zOI5mD6c$wMQK5t>&4@eTr zX`yU^qcfZhAb$59JPh9fwRsN)!Mooo(iWvcQ_;x6zP@|b+{lceaLmS|)S$zdQ|s1u ze`o(WzWe#$;=knJ`n$m;u2s2{C)CW+pu)l7=GplwCheZNc}~y5lXUWMBjUDcky9^O z_d+KA^)AP ztlw3{Y_N4+O2$=bJrQL+(R~(T68ujW4;=j0=&ww8u2I(UrpBjTE7KEXXipZ`RQ2^e zJ$#O)LC42e(I*z0jJg;trQJ<*UvZRFh<-2CB*swf=QL=|4gw|RTnvU*t%YUk51uL5 z^P;zFa+O9Ms;54^v{Y_kzGGnTq3y#lX)i&6Gf5{?N4Qt%u*_#8nikNCP(yS@0E9(2 z{|g3J)2S`cC7(a7LtSaCHmnsFHyBbJN|@P=sc@6AE+h$Gl)iX>!dSwdix36UK5?BOe*7udYX}8QLa&i{gkCJ~ zSfo(TzqPp!9Ng^F`f${@10XLu$@zWS?=@|M0 z9CP=1^Ts_5e41?7gqf#^wP2A=&I^R7okfC=>InI`4~56qQ#uHtH7yJ7e(bb|!0%&blj(nu)g^O?JwQ zaeBx!w-1h#2-cPUC)pFI&FG33ivMT6M(tXkipg z*}XiZ1QQxn+`8l_g0%u8iaBvAu!9tyJU)65c;`sN!xkFLs;c(7S_G$}!F;Tht@wg~ zH|DL%RYd1#BXN0RC8kCX#A*-~*^!@UFfJb?v~LP(?+5NfS1Do>cCF_SI0nfWIez^- zQ<}czqkC>bx=d;AuUjJGFS@lPUHF_pu!l2cWs(kK>%c6Z0S^!q^I}D>9rAcJN&_>{ zgvQ(-{Cf2^Th8VF9088Wt4wpukk>xybpOWxV)mtq)N?du>vY3^oCbg++ReTR6-@gZ Dx9#@L literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..77c491ed6b4a73ecab6f19e77f901a45e7c657d6 GIT binary patch literal 1761 zcmbVNc~lYz6E_cBN%=HUEKO5)y_Q_9{a9CXO#(_KGB;OKNYvC;MGGkpJXSFUOL^(% zQI>p=XBuEe#=1zpEl(t`yzPr65Awj4h| z)zsAVyv`u}R6h1E{Q{t}^vHl-H8nu37sB&G(*4Cr9Qwy7lNT%2MwRMcM`Y&|u#=2m zsNFo9H6HrAzOmI19-JsSVh9eKS#MABJY#l{hUzU5B|GCx`(NVvw9I@LznEBMlUbEY;Rr}u^Lf?uu5hu3aV>rEVu&g zV5-iI&4n==$1vw?c&IK!$<3tV`F#G@2}I%#l4#+=!UB}N3H-NJR>bAYmx+7^+9tTp z*KTfich|sCuBT;{g+w5@H?md^`D`vtwX{@MM_P0!0r^yXYVD&(<5NRJL!{~M;`G+2 zi&Ug3`iMT%EpZ9k;$0QAeAVT}Z{IqO<8N}=^n<{opJ)9 zm?0S{rP!O-9?b)-xxb#!KOCEyI@y?}-1Kf5OOQ8W=Pp9{&Wn!W(CVhlhW) zmvO??HE42pICAv)HE9j3Jb$kAG>hUN5D;)MzumNreca7W*-%yW%EZXXpFAVF8xpc% zb{CD3Z){ARNc#9LGrXhVLF(2=37V^Xk~xRh(9jt6Wk(&myPGs9g)@DJMn|{OaJZ?F z6hSMG!&#QoXfz?h>Q0}G1S&_{_}I9e@0 zUnzWdA&1EfwE&R!KmKU~Y-qUVA0NN0HxqHR@49LRWkHWGWz6+4E#PjJaw^Ei_*PZT zIYyj>3@X$=tSiu^>^ntr;*2Pl`yZYbHcl*x>OS4K$jy$3jGS5cpKwp@z5KVg*>iZZ zYO2gP(d7{0ZA{HPpDbUhMLks96m)H!B5}oHaly%gK^ci~DBosX!{C=WyeI_2pIXYi zXOhV*i zcg?Mfy{kmapSSp~LZC&qRm*&4=QIIGUahGmvWW>9z za5*rd+`cu32T&x1W3jM=s}j-9;Ut#?$^FvO<{VaS1aukV#G=p;EKOce?{>|n+}zxj zAN~A*V{dK2R8YGXXM`+NZB|Qbt2srhN4kP`H3Ru@A>y#cK=|N#E+?g-zJ9*BprBxL zdwUx-Gw@AW+VhhnZed~JhE0*|26z4oP4(_rnCXAKBUW~B=Q=t%a`RE)0XQ6vGO?J> zO_&wGj*f{DUa$k#XTzB;Te2|~rge378Sut}*R$cn_wMao6HgQM0-?rkQWbg3XD~H` z-$(L-2tP35F?APa{J=)0d4f~9~@evl8aSEXlt{tO%)XsRd#aH%>VrP6=Y(fysE6MEKyN0 zi)?CYs!;76klt~;M-Bzsx0#G^LYg69a4qJ6>qZfr*(oKPw~vmFE`Ip%;hw6M@6vIa zrBt1EZXlF$@#4jKFz^*n#p`{+Ap;A!QE|=Wo#Ds9pqUI0FDh^on~||+mmnpB4j3HN i6`22j*ZglUVa6l2bW>^`bNHF+b*XtFF$gvspY + + + + + + + diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000000000000000000000000000000000000..a912488fa2c7746b8024249b5e45b2fbf30430f4 GIT binary patch literal 6435 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97f027b1c563eea24a7db514280e3a65b76d1dc4 GIT binary patch literal 4987 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 XIu-o%$Fw(1HGsN`rgF6sCggtrFtI@d literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..5c4d1e0481d81f7f99a93ad5b9071e3ffd40b51b GIT binary patch literal 3187 zcmdT{Yc!N=8@8tj6{2#;Fhn+$QwTX$C~`cnvm@I#4rxS*o?uLB&YGcwZ4D*$M^U9p0(chS?jsiv)+4M_jO(O{bstlT#($W zxK~6(MAE_D)(zOd{CLDgfp?~=4o^fx!py<;tb6C@6$5WC*GW1HnJ zc{{&~C?-;MuT$hKSX5k1|BN*5mlSacXs-Rt983on5|Fa>umZ72GxX;k+Fz*}Q z$Qe&SXUp2y*j(7$*tlQC%5f22IPvh&qcOM3m-&_Snd1y29-5K(?6RNV&OuO?9;ode zhr?NgW{1=hLg7`c1N*E>mV6kH&yqMyrcWEvl8bCBai^&FoFj@ohk)Ad4EZHvvgk92 zT6D(kn4f3(H{4aL{Aafcf{D;SBgMmWB_YOt5m~B1T`9Z2wYRsI5mC<%g78n9o8x_5 zTwHMT%5IVE>zv(%gt(vig{MqSO_dW(?2jKi=1B-0(St47Yl4l>VzEMR2IT0>^zN}Uw%Cm#2~`()XC*l7z_rX(P*$G2M2H-#{$~_ zAcpg?;B|1X;w62BdzGvQVd4^n)XA%H%M)EKC|1Jk)EU(?fhTSKP4zT2LxjbaiN3c? zm4bd`N@P7aE-tZC!EL?KUu?--q^z-M?okf1V7a182rsXu#nG1f)i19CQcGR!=Mser z3kx^obPx?M8yk^jB<`B$>7LK!jw%t6ky3CNjIsPj_TMsB56yGAa1$dVG67AqQ&(3H zF*7&cT;nm}pBaqT^tss98YLQ=AO5kguNZ+qYzV&8I_ZIf2XMQ@Mh+e$Wp+=#Y((-# zN+^Opb5uxR;HdwMg6`uR)QO3SZkkH+Rt}5BV!!e$fQo;7#$z@uxl$&hQ@SQD=;-L+ ztE;P%16CCh5)$kzEiI$sVq+T?zPw@&VK5lY>umyt!mgX9TWPhY4Gj$|($AIF4-^~0r1RtU>@>D5wsHxy zm3U+u=2k=mZ)#t%rJ=F0@ox$}#{~U_1ZR|4(fEK7Z{*k&!gS9H`Mvh!j;4cqD@o5l zf-T7~*PZPWQ}I@d|9->3z<^_3)VKb?7khE}i9<%G_6nNvx~8VaGIPUbxVyU>?@@zz zL~JT?R0IcK1_-IlgtGqUnM4A^q)*6bdGr5IXKo(HH|z_tu54^4T? zx99a1Lx6%}fciC|DJ)vJG4qe(O%pL-x$>>8t;ELg;ppsnWqYT8T22lU=t&iM z-NV0^hqkn|G);3jtVq`LUwdfhm87p80^vGeefaR<6oo>Gawx6pbS8hw#AGMW@2Qg&SZ+7IH;*|o5{5Oo^+sPppzir1 zN@ZEnXQ(5J)nL*?vwI?aH9U~p>H_`_`Y#^+6O3&Du8dyIvVEedC6z4ouB2j4N(xAT zRW;`!H$jZVTp65a19p2OE+QhL;|j&*^SgKNhI4Xc0^a^ETYllbQUZLip&Ax-(HI<@ zb4>xufK0vBzqh8l6+LIc@XD3Mc|I&C2=yJ^ES*>8|5;Fwa0>p_%f?^sl(BK7u>>3E zs`W6}FWJW^phqHt*%+}J971e5st&5RABr1#4-{KMN=oQ3vDSH@=Y?lICnzY0UsY=N z69i-`iEHga*_D~~&mQ!zqR`OLgvN>r8d;7Xzz8@R7!u+~`EB5tN<4)n3IJG+zGR@L zpQQ+aRL{@P`?^wW5Gd5z5vsm$bYS3?{c2vIt-o9x64_2f+mK_CNa(+eBCZZL33)sYW~2slC+7m)|Pf&oIA!^o^|G)dj%*q{Wq9GI=vVS5O2|6Mhgl-U15BItfah zOU=uZE1|J3Q`Bdq6&gxQOFN__CAWFqI?};_NnR9}K==X6RLN>qu-;naqIszSY6F%_ z!{Kn_o7=+eJ<;^rhcz@b%1@s@-GuEO%4%q6Apa&TZEqJIF6oB{LmdOJDFSFDS&cjX z^m&w%5#ZWA)QtV8nVFd}R9fvy2>{b;`AB*&_9%o@TbtYrT-I9o_WS`c0qiZw(_dvb2!|3A6%|gcZEn|6zKyUZCnpCF?wqx;p)LYTSXWmk zCa331UhaWJk%uFj@I$I2Qv$yn1Yn@9T1G@PwfxSx(q!MMw<;IZa&`e6yZ}gi4^{uZ zVmcqGb;vBSDnKEfzcw&1kY|%2rL3!^mH(imq+|)%7By(ZLt52gECFJ!Ls|Owf*Iz& z#3AUv_A_jr<>cg?3>j;UvmR;3Y@WcIL}4E`U(f3F$F_O} zi#A5jd;mIMF3=`96_1HLGqHWZUyxjAR$DCDxK+GQjnU? zAr1<#?bk%&JRFch*)B1u21q&|;PHrGdEQ;twA}##0bPK9HRPsWs869Kg z*GA&ffmtH3!llRz;qGqh?U&NoZVND-ebe;Ajj%8%sj@PDbgSH!fj^MB-usl2xYp=a0jO_6ZPI5ZTz+1hn?=KFY?% zz8n9;%MJd;l?nEJc25{4viW+Qk}BSrTk~TSm=6@ zHF9zmNB(>8XlPKw@@;RvPxba&cJ_}=X|!L?ui)t$`|{L1s9K?8x~5LUM(U8()~S*1 z?>t5BOF!`pZoxio-kG~CCLN<<+y(Y1J`DWut zW$3NeXn^CN7u{7I+YZtbd=qu{F~zNg4D`xaqer@rVpc(38P`(;q=8BE0#Rwd>x!QgL5R(!FOTPCyIxU~p@Mlj(@vzv}h zA`O;}{>`ys(vp7k_iNxr9yFhdJoO0mCofs*8YPICOJ=cq#8XVT(EM~+Xyuq5N^g-7 zq~5oX0p<0VUg3f~Z@f^<-VEvMt>QD3mC*a@YQ#Vwz=xt`B{0350Z=*4ewA-v2>g z=cV|Y%WEmk67b7%|5|;Wv`-ZR=*TJ!l%67`r^CJLgA?DFLuj?Seh2|eG`VJLr_AhA zM+(Vjl2s9#ye2&tzMu;{6)HlIR@=sW(%6V!3}^IuI_$TeYhfAVsnf-7vT%Ha+eY{w zQ-vp$^aoN_CqN;=CY}GBLNdhQ3+*(=jLet$hbvpH3lPUAcUGVtmJc+Mi(MYymg|Q& z|KXiQySif7^7HUDy4WVQ`L-e*rb?L`DAwKpQ^Gy5v(ilZ zZ`)8!l5@x?yEoje!tC3Na7i*^hJ_%9dOljsX=&B)c0Y2l8v*8M%0(`ITn(SfyMvfr zL761~tM*l^X|Uh$XuzP(UmKZy8!FAXueyIvq=zZr_lk)xP8fE8UsiuTnd5eBtrz)i zO8SE(*cYNq$sVNDv1&wNyCO7Ie%PDVQ6ra7G!nUi4_@Z*Fz3ugK2&bO_)+yk^7yb3 z@Dn=J)n}`SW+Gu<9};C~Zt5tRv(O*WW^362x3!go797`F@~!ivD7@>#gu%`WJz1-^ z-Z9XWbI38^e?RT^DVzqIQxb({TKGh71BN(gvxDEzpz{+Fm|U4Yu+A~{ zWq-i~0=7})a99BCRr{lE3nwf1TTBcG7%^8Vv@|zV1>J3x3>z`%5LVAf5TxGOg_PhO zU5eQB7>+JslJYH7Vbzhp=x|^+47U)Jz+_IGLrSiOv!mgWm&D-dty4LdWS`V%OUoCU z_>c{4lr13YK4$fg-YUT#0w!UU=@E7>LK}Pv_ryy-T0K@!$`VwsHlzYMsNPJqY z@EVf+T2OKK76v)`Nm_L6LwbilO5~yzB}qY%qdFo>u>I9+V3K$6qC5O(VcuXG&eFzq z+%mw>BDQOhg6NGL0A(Gd#nlNN4>=O+()Ch`^M4k0SW%4kT-{nyVfUoPz=&CG8WA<{ zCijt`#HP>mtJFf{Q3l;mDr-G~dUKqJaOJwwJ2w6G>&nHZH2KjLn$B*)yJ%0KUa>Jx zn}ZJ|_jpshAXL|rRkD6}*$GWC!fe0JByqf!fsOx;09(Nwu{>|(OvAv|#8R({-lm25 z(*G_N(1+0GBLe83!Wti1baqq?3&4+GppGw*i^6UzQMnRT0-+hYk-lGGEjU-85(_yki!?XkIil@}|s}Rr(mBIg}=->32zEVX%VN#N4 zaYpa8^2>=F*POeW-fRL}#Vi+45rrNGT4TXqm@TK960nr+M*J&z%Vr?QXTsmedYSrt z3ogjv>SgyEprW+pP+f0w1LX1)9nuEdStp9VZ9NX~Ka2ywd2&?|9X3g@SL8;!^Lphy zMNoqccM(x$aM{Vavaa{Y5S2<`#F$nuMZNDk(`xRulL*vh&IjNDVFsNW{Qn?t`kK#< zu==EPd^UW`JI2&R3}u>2a#FKDwlQKZX$YWVatiqXX15Tq$!9%Tpz?QyqmBMx<5JeK z|90cdY}2~O)+9Z< zxuDn-$NZF-^qw3+s-=U#4A@fAe_6g$h>fRgxRv>`MxSPVj|b!nOQG@G&!$Qa2~ah* z>Pf?@PAloKF@`igpi2ZEyh-%k8>s@x)&AiOG2tMBiU!UUx&c<&>ck{5Vq7W@wn^ zVAp7hKrM$x!kar&$E-CGH?=Q3EI`eBg8{P0;liqK-;mZ_5m;N|8vZA_$vX07F`Xq@ z!s}ln+7h2}IP!~$R+^n=4|v+z_*Hpk%hAQMB>}Ih@|!aB$$B_3o*A6gm)>)h{=jNL zdbYEo4=E<>pyz;rKTTM6CAymU$C%235mfcP%;ZD0;Jp#`hhNZONuY(a#5gf-&+fK8 z@C|oiFkBuBcawZF(ssN0AJ(=Fv{!bHqbcxjV%JZX?bxRBF zta5Q}wndOCv1())Wa6mG*l`YNOB>gJ*CO`ruz?r3ubZE0aUsr^E_|N^h2M!mh6Z0M zbuL8$3yeikTb@EuhxKD&RaQvgX)dfwz?2Ao^7s^e_GV@8C1;eXUMva!P(AT6}3(0&CSKAiUz>6}PVfd(6 zHA=1kpF%F9xgD{Yb`DSXruc)}W*V*W+_UBxrP=h#-u!uWkRt^d0)V{{qD`P$Fo*qO z6f*w$Ngx>N`;eVocu;}dOZJ%@M91IGG^@Pcg1%ElTHyqW#&=7%i?cltHc)s>tti$*f81I&_B(Wzw#D zla0?iGRrfgxv{`7F*Sz14Kl9hN^W{v>I$Jd>mRBsm`%h8K~c9O@u@EVp~3mkWR;aW?`(w~>n2yi z(-LpC&Je1$9qu#+a5Y#^UMAl3XkR`;+z7u!CeNKNFLb@$o{zBbY3kAqEC=R{GB?b1}fF-||U8y-GxxI<%>6t%4(i+!t$8z;CzZhtts4xx;~$ zejk^$xRAF5vpf!3>6IEpf3+p5EXF1M8YA^iTR6|C-7}icjn}m2Zrl}1l(Dhp*y9=tL0WHt9O%g@ z3H4>kqG;OHPehawxB68Gx2av^KE04XF?ndVJP|0-L>G)ZMg!7m29=0Fg-3Y z5uBIw7P(m1umb%9;8J`<_9-cUx?5tdz9M`f^v_DOrcUo@YQ5ptcM-`L;#D}ghK9Th}{;&Nk z2KKQz*ED?ACNCE8$B{>Dn0eE~f)}4}b)?feW>yporPY9yy9C<*?Avihb6S5L;^u4L z5O8ssL%Kg5Svo(C4ceew1+`N?w3O8Imq#_S99aw#+>lL+P38%3 z9F+8-lbquy=>+L1y_kBS}_^e@_Pq&>5*!b<+axT~zA^2R1F za(+z_(i(D?gZS>r%ILypX?BR2l59;!C$ptKyNCuCMtvD#zs=-XgiWO9x3PowjTu8YX99CEuNN29NT7}7_v)qYv=hJz z(OLCD*+%6iaLa6u-vBSTXMzK708h7Ve7tADg~p=fCKnrmj5F1^NBs$@A7xxGLC z>zYOKffwxOJ;Wv}y~mv}V@J;$rCRG06b%J>k(aRN?%NC~%~0%j zD3k@9W4)hoO9SBl-vH2d6L!Di2@+cN-68kQWZhwbc~^=9>{6&KiPAf{Eh@nN^iuRP zvlL*k#|lW{jJX%R&D@BHj$1c4^Pw+C1As$!4F~bh!Moy6_Z|<3nmrNSpwf|SVf2eY zv_Ne%0N03t)a2=zOeX1h?v5~^Fj>*o&pVsV4@qSe*}Zl*AKNoy*cPp;!wQ&NEV@ZA zLk#lyBTQ;8^1;2@kP4I$Aenfsj>)E`@!j(w&wQG26j|eXZ+2bj1G)S_~;n9F7=7ncZbhZp*4aWHSFh zFbcCne3Mi1U09&+0$FFy4?c=q1G1{s& ze>|flK`XOvEG*V|GrVo6D(Bf}Q~2m+2K`O`4}Z>ug(no3o^C0t<){D?s?Jxv{_gJW zeA-(Yr^8IIehKy08RTSP*GZEzCD!NNE3RIL$!%*3V8|UjVJ& zQ`wrf5-P{q->YH#_JhW4UlE9M#$zrc>*U2Wk4eET{ZYwY1+As*SXyrA_*|7hOv@pG zfAgXSk0`5;9Q^WKo|XLV1?AZxe8+?j?|ARHVb8)cs#-0gn|S)&3{@oQhW zsT~nMxheLbhH^CPcpb;@8Ayx#!m z1g1ae6jG!Q%_MbqptQb?AI zD$&vxRejCToq#PxxQrMvsb+{xXPGBR1%CwX<@`3c9YNYF0s&*808lt1Fb+$xZF+b? z721%2m6xS`028UeK_Hk6OVjK`qk==tb zU>kpA@xQ6`EUl(Ky=T~$thP-)9y_e@K4pOf7S#`_b3!x)qmPYtalzXL#m?Bn+%5mI zxQ$b^8YfL*C@N*?%^}NDmIMZ$$<9?ASQ4$g+rx|eoS2nF}j=M6g zx3fHw*>>0d?Jih-_#%T|=W*DM?yEnqCqI4c{oq+x{j~HUo<%YJtft?> zEI)CF9cyEy0FVFJ|3fdl#PtFQ>SrbW_O(!x^#dYip7(HBKJp``FWLP38xXnilzmrD zDaZAk+zF=*SKB>Cpu*B$;}l1c!?_SuS=+$7xh$0zl{Gf-qF>hLpmOjr`&PQ(34ZkQ zi9r*LK7TTTq>u1}%$J!m8LOyWW{9{&2r!!SzRT&iHMCNELHrTr8$^e-3uaGWo42_u zg>&8$f^bzIBLc2U<%&G+j>`-B9=7YjA5;t{djj%j?tQa63gRcgC`8l)vq5YOr5~Ra zSbN-m_Sn+4{5I|KQn*dtcp2-^!1KrWTrO`%fiH-vovN5y%C+=Y+m|b3q~mv*vozR} z6|W3}qs0zpUlhl+P{%*68=HZwZ!QI0xRS>qG-xZNpkZBkPwRzsT#6F9OCT~@LoCrD|snzgsX(h8vz(HZC1z5hxF+m zzhc2z3*crg9lkV{z+`Xvi+1@>s{xV4#ii_Hf1zNKT?IgBkwAE0CBJ_wjF#?j0%uGg zt(o&esR&E5|9(mxH$dapKb%fmFR0_HeKqT0yG)L7q7aD6AaB`G{hMEf42VA0Uqd1;|%g0&`^3yhHmD z7~sxC7nw9?f3f|xeS8Tw7O>-#5G&GL3iK2AAJ+={yd|^xHw9tH2QAtOhE~`;uU+^p z=*4{wBG2x1@Xgx*PG4N~Azv)r*>u|var>$wf==$WP1=?^Dphv-pxKxhlsZ1rdh_SP z5^xF?ZAquSqO9VRs!-rSmAMwT>R3Tv4!Rw~1MR6(RTCxWg~6%z5nzW%z!HGx34U7Nqm~e>s2@R=>8da)GyrUS);7GvUF6 zUP!V4X72SMdMZ6PhYoArlY|DVGkd`}_40!GvS;?4bE3m(D@nr*W}TtlWc)0#JTksm zf)qvD{}SAj6K6$UF3fU4t%#_4Dr&y~CGTk%GIKOp37D>H9=4_-Lh?7OV z>03VK00m)VC^V5W^`7jB{rkVTgx{X&8%Ag9b z4qz(pi$gKiOTM*j@@+m=_7G`RvT}U(M~)OmYRaCm>_0K#)L|c%fF(f{QhM7l8_(|h z>mV&_@qbxAS@4?dv+tk(Fv^T#IvBD|kRSGiXKtImB9>FfBrRXl%-<67K# z7ZnXb)Z0%g9)_^k70~MS41h?|?*wQy#!U|g#OYwoW*bexrjh)K^y1pn4eU|KX!^Pr`!aj!dz0hHr; zbuvwv1HF3?*?OzmzqxTt2wZc)UF(W(pPbri5bJ5mrZ~LyrVAfON^CY&=;Qj05feAy zs}Yj!KYpLM5ifyccnMCv1ECkaxkUSvk@&@NYUT(dBf z%u?Z=R{@Q25u4zG=j-9=nr;9?FL(m3M}R!&vRno$sl4NhAB)UsVKOu<21T<)k&)sViFg&UzZPR;m%&5BXRMgvjm9Z#D4T%Afg3?7m{iN4CY~ogFZSwy^cv!=4giAv zMacQNF#h+dJ?@uJrVwo7_p_ee<`z2u5*IdaPZpW8Yd{OSxWhfy%TT)O!h;R2O6Q zxc!PS>OB5)?{RT^n@IJD-DVJ3Ga(1nmhuD=Z1Our>K6E1= z_TbaKpsHXM*BS#}ngb)I{R4h9#3|E3r}*p!2j-%C zIXt)`1$VW=f|;;dw?Gc&c8L3?5Y7(7okD=E^9A9b?8!0uEBp6tRzj{CC`eem!KQc^ zYo0UDg-HBB@qr7u3G+M`v@?`uxhYu?%W=5GvVr*9mLJnC0x5{Uxu}oQx5Uyq9zHVG zEIyGlY3ibAV?^%=$(((~^$TRFB@};@8fnktqLw|IyE)A=Ph)~tL4>+L{v;v^nhQ^G zZL|iQmN%V}0?Dg$cve1%-!TWb+B^90ShF<0ulvQ`Se=n@G8`TBubKjfX3?vWjOJ0I zOk@9`QQJ~vcdi06_=|TNc)Ecrz|X_<=Sp`(Sl4`KZ#)I`dX9U}UzZqMzA6SOkhp7? zxxJ`Pi0;?SAILQ$>}y3>l$?MCm(;o{1b%Z)eDR|%GfyVC3lOi}n^`A|fuJgcB`qlMu0a^x^dS;R zB&YSGQ|ct}_R7|Q;$8A4l=cc2!e;oBy!r3}cQ-&cep12S^vlc+cHgAcfajIlcKn5n zm1ZB$vm{~+twV_yZGEn(C}#9k^6G}WHOrajCFd7)_&7nhK0W&sA6iL!Wi&LCA{5&` zGT)NA0?p)ZW>H=}GytMO=jD38x@6NFCmM~R}k3QST&5n}eP(U)BD z~GeKy=yX(ro~=eeah2iU?EX*hG{ z7=BXY{m0o={im|k8?k9N9DR&%wfss;kFT(=HT{c0zxdF}O-h|)F?%BXSznaLDuSnC z6MdJo#_bz~pohja_c#vh0(6wjL9~u1U~SWr2D;$kBK~ZJi{-MxBP`R|`dlQ?(#a1x z-01X@3z;oi%HWkokDez*K@%8jr&Mr``K^{>emHFi0^nRW)b_iMNST^S7=yg5Rm(>% zy)>SZ1=3DSBJ*&iSy5sqojx5(%JaFM zba~m0xkn4jQ~~POIIY|FFKc=t(mEl&xN1}fPD}F$Rr$B_M%f^7w@yit^AAU}o<3&m zL*q?+3DC<$9Cc49_AGVX*dDdPBY2SZJLL~-06nbEKRQ39);WG`=N6us67>~k!}{}K zNnBL7#7XnbaGDUkX3{=>P47kNV>2rVgg=1_D-keD&r!7< z`PMo7*y}d0#U|i_ctZHWN8H#0Q}8@C*+i#N%vN=|UEm8HcB=eRU;+$EXPjNO0lS}~ zktlc-5$cj@B_Fa1zLiXI_GzxrMts}kpp#FPfbQ!$>cNQ~aq`p!kE}oz-Jj6^|6%Z4 zb2r1>$JczhDfdla^PKr+LGN-%E%ep3-q3cR+9t*#sW=MmYfo$waf10TO*uEh#??dh z)=;qra(ju977*;Z|M+{b9nA<8`Z;(^!* z5(|H3(YYoCKF;e8-!?6~TjcOPV8v?%o>o(jN%;M6G;>>M&qPvIX_HqFO9sBN zfJRuge07Jn@q@c;5wpBD4bKl`;42#vL=Vd!Uk_I$QYPL8Z6+nSn}hejtqFkS-s9SC zk5nSraYP*~UkVYbYw{!?n$B@#nONJ&aJ2nd42AR(P2G4v2hgD`YS zN_R87X zJr4mGip92aFBTR{s;Z)#p3m341%j_;`q=`9OA-yGern#5s@?|jI1g~-n0FCQB>Bv- z9eq(9dC$1P!EKwsz{3;&liN7(i4w?CF+>h9q1^E>teQ)nITT_f%4-!2dZhAm_U85b z0|{5n?4Uv(C&0LQ!#3*R2KIe%JfOL3ziK#8hnY3MMQtuf~L?^wbdmL*!X3;yuh$wFWX8`*-aUSCGp!Zz?X%D0v_=2XMs8gD%n!+FJ z9BaBDSoHYHnJqoZ)#uI~Wx&E>&xUgdW+>p=w{6U8q z9uRx_d$26d==NF)>GWAOQuDdz#-Ia&JI&!S+RwW+vWBwx)C{!UB}Ki(@7E4!SVB*< zFMV)zqy<~Zq!)u?PG1ANkM$_SG_W+iRPA6|PDLhfDCiaNr9+CV;hXGWaW-)pU{n(A#{;tF-{T`?aSQ z?E%-PsMbIHZ`f;4KU#0yH&7Z1b0&ru$)qxox!r|H@hqF;6;OdS z&EItj7QYUnX`rjN|aX1_IYU+%SD$fX>#oMbf!17#(kC*StCWVfBL)T=xE@Fq-`8SxmlaFXGQ z{Wi?KWutcAg9Q(1wta{v5JX}|)yvQ+{E!?cyW_EAaPk)Ha_2R77ZYw$92*4B1yz;Hw&u*i3m^CJb0<}_1cW?QK=;%mVU&gwz8`P3&3sRRP$wjA(v5Js-2a4c)sdGj>f_0<)bZgLVN;SQ%ydDI z^v|s|YiI9+$7ION`P|BJu9t6|vIy@Yca_WCf!#iwvJ%rn6<#MmQ&Fwrka~dnyNxe5 zK@$5o9KULmhb2x?S-JiXz{&S>`2EBt*b;`q4S~{)Wfk78Nbhe?Y?wdioHFJV1W%6J zZ7HRj&f{%I$cL^b%-vNt5`B5{)c;yHQf?C(`O-1EUzUWQ(mZ^$g4A0;Yhab1yC*IL z6#K=Uym^u$zMr%xY&Jex^Q)0z`mcggKV4)pN~q0k#8~*LhEa{vZ$GAw$;WdMO%hR+ zW%;=pw*S0jov>@9TkOx-OvNR(WVYzN#Q2n}@Uk(ExiD4cI!T(uxW;L=OINp>Rp^|Z zvNIS+h1d;pP3~QdluFbUwPyZ2?>Te5H2H|W!0Y?nLl45)k0H-YG@n^j)g?$ep7W`F zZmIEaWU4V3&j0(MI&01-^n|<&gj=&V@5}kB>G#STEq3we0+rNsR)y(V9H+d^%`;4R3dgjr}Zb{hX*S%yywhGzHrDDmtx9n`d= zlv-1P*PL_zilLd?^;Fy4-j(>(+q$x3agc?YH)4r_$Wjm*tsfI&q$BmS)fBAF77&#j z_9sa`UZ8O!3d_FoDcy4d}0 z>IajlIW|u`tN@lOjys8=7fsgv9ZKh!I+e+IEKBC?dX?y$Z zr?6ou8Lpr~Wswn2Kn!<~tM5WuDpAjegS$DYk!P!R#e62G{ki^pi_(v?;+LL|IX})? zIt_st;en!RIL!R4KwrJynO8#_Zh$9G_9-Bp8>Z@sBCGC5;2Or7$FvSfNG|Og2Dx z0(QAMkX`-CQ~B)d*MNYzkX=?IcVFCw!;1I{LkM}k)4&-$bPEF;;>5u*MuT*{c1yMmHlavx}F*loY3>MJ+j zDUEgswrx3@3wJw}D8YQ>$7x~U?Ln@Sys2G1v4y#W-wpBOfFgk)sr6~4+l48PJTc+p z57z3L6Cyh$#pwNzy5Z)txTm?E&?`dNmSfya6JrDRbJtkOAlf*pgpdp^fmWF)SBs{{ zR<*T<0sH3jp>TTl%Z7tja+0FQp*m9g&#Vr5Vg<~FTo;Gz7CaJtQ5RgzLvN;>pTY;_4Eu*-8>-;R#v&I!9|c!TAG}35yniOBW%)c zG_W-@=gT&B>BcplZ@Ox{8PA)16PfNW-IE;DE<+pcTwZ^|sBJuN=m6#W$chTsMF z#w(gG|3(Sm`q#b7&&6xTZedeaae}vkaBJ1UWE5K!qBD)LWU*9bY8M|yG6b#ep;gkW z!)2A%f3~kqauIv`2&UE#D_wRLxI!q`<&^~1ckolcoh+0MC3y0h&3?f$jy}(K5=X6* z!|TeDnL~@ee0YaDf$K#{YL45m1pa-;!u3sE6|c$ZF7lOZ>+LHjs$RgNwRxw4=Cw~@ z->b$%GhvgVZ2F1)7LS296&Ewkv-P@?m96I?cEMLVAKqZrACdua$$72Z5RCw_Pt8*v zyv)769xUD%lImLc;Xi1SmFB^gw8UXfoQnw9%vKBZ*@N%ooP7{zt}{a&a!zDmTxp*> z1NX5DaGmerYzlZg)omVjq5aZ!4ZKgP_3UayV?|;X=YU&cB;2|SqMlc$$q*Zvv-Lvn zMOycfji~0WCnlX<8kN7tH*KO_7e!5xEOZQh5voe=qv;O8tPL?#-ubIf%wWjAZcbzm zpQhg0$;Q?>!Ky-PppYF)3}pEcTdXNU*2iDYALGXNV=YIQtI$v*xEPJMwo)1qa$q<& znC%=Ih2Uh$f?oxIMZtJ7KvUZa&v+3SQz9ZtKN16Km*5o?lLqpAyCbrzM;c7vB=aiw zYnSb2!*Wu<_9}n>Q|k&M1@Q-rI2o29#DpOHJS0vX)!{Q}GZ9V|ZUehW@*H zm^*S|b(I?l1{+;Xp{AAM70xUBib=mKlj-djHH)?57ZE-=oPQnbvO~DK)9SK6!II<~ zqsI#D<_0^+6WRrOInDRvUDO)jYGW1H5?EGbbywU<{?#Cy{*A|q5!MZP5)(}vr&LEg zN=pYZmGwoa$_u8>NLGxus}y$pV-9hKD?QlqEKYFP6OUCH2(h7U$V$LV08}&Y=Nk|M zShdZLH8#42vfH*YdtG#urv$fAau+@3U8Tr>v!l(hxcBh2=cDRA&ss^g_mlptIjHUi zMJc)oUUfbauV%H%AXQw?8o)iIm#G(`REr8xp`^hJzg=@;7jLLERwWkPK~M@x@9P*) zVMIBVf7EyMnG^7qAW>z?r|30lcKoM6ynDP=8fovX6zmwTP@Tp=0kBT{xxu?=lJ&yX z!XJ9+2&MFz5ftWDjxb<_##gb+Qw_C}q_!t=dlP8l*)Vg=@03Lxo**O-*J5!&{XG^t zD#aKFm-QLGg7$tjSK3Uv>W2}+o_-rRl9idnL@dh&zTNIK@A~w~UXmsp`p(_j?|X#-S3Z@isbJ)TO3JV80X2` z{)40T8@IdLV`8Lr!~E~)5r)}dbGy`~^LxF3e=%f=ZNaErA_f7P>z<^fxvi9H*Pzv& z$8b*Po^tO5mlO&Rcbh-Re-5z5shH6+?gKbEtv-0eO$zLOP)jBDfvq$SC9RK*;cD1x z!oEq-dkg+B-ns0TYN*NFY@-|9kBuuBvJp^8K*Y4C6cop6 z<_|xP_G1jaNK<{aMK|l{x0K``{(cM3LAkTbK^Rc+pQtd&i`2I*ZQQZS>7Nh8PF*xk zNbkL@=X;njy$_2G}$*m?Sl{6t#uKWW1;c zxXDo};Qz+~MZZbxIe+NBOekk*T%CS@_Bg~mzPI~GF3?r^ZTN2o^P;9C94fR1JaZzj7g<5)NIUBawF|${2mMP15Gn=AAF6> zP{DXY=cSdIZc;n>*bgh_N8|(mR`O)a|I>gYV~z{qI?4)c(zAVHKO>0XKy%c_e z8Em2A5g8&f!6+x0r~fqfyKdT>vKIzy(O26$VX_Uue;h{f)A;NLfaHV-@qx9oKRMtG({~ml;jKVP4FwEa) zG3^Kb?E9az;xOTBc*E{CV5$sKNu!b&jZmb-1+b1S1M-|pg61MRGCu@o4@V>PFjQ;# zruTzTv9-|!yGL+w>wxUuiO-aoy>ZLqO$~u~)A?udu4rJ!v2mW)pPzTY(uWvbW8!*t z%4$LC%NryMt$>AAjx|wIp*wQk&uK+)A!jt_O}5tR@C7u2|f9Lqlwr@b&sfG#C*0qayV7v$&u-iag&@ zy>U!rQ>BPP9$toCNN^vK=CZwmRGKyx=#;&8U|42_j{2F`Pv zdWBZgGPc4L917XPu4Gwn&fs+aHjlnMX{oCN2^jwVK~8feb<q~kQCCYI9uXR{4NzQ2Ewd=-{; zu3h+RJ)gJQHtWX&7o@f?rnf*k8(AI>OF27Z1ixI(_7-cnV$gH9;lLDepx8ePCaGtJ z^5~D%JcgAA18y)bLTQZGO>9HUV3n>U}Fg6=JServQN?#9*H->J^C7bQ2iuV1gnQcK^+qaE^C z0)8r)5OM9~(mVWB`uon8Es%!^5A(8S#2T-J{(*t)$6i#*l^E9i>DqxW(Q z&hjN-bmt~*jFjJsi}AG?`$g%Fay=whB!y6e0~7`Z2~}>8iJE{ECC9wlpcYEe`8jGU)OCQm-otb%P?=vTp!eR(ck7d~T4Q znBSk0d%wF;8}wA+X5iINwstqPo#tG?`NYC1+Ij2F&kuhNy-JgKcU~-h7r4?q#Asew zn(jsMkXPP@>_te7`z)Pj)1o5dRi>2{|DPn__j;4%FO5NIm@=D}@2~Rw!zEt2Pes}( zrz~8d46H!r?EyurR_)m#BmoY65jJC2>DPF7+52@HHJ7%t8ri{YDVtfYr;I*pEcnB- zHi-+x8YNX_w8FPGiw(~{D}A+a{Yr$~o%NOylew9XkrnVe3@>IfJ>KTA;wMZ#uPaZh z)t;Rl4V#@LL25$fIjLayk0NIA@{i=U_*EO)`$T+?3UsV7CwuXDZ@Kc8aB!(Xh8ce3 z>ye;2*_f7&6hcdlya2M^|Qhihe zAqP>4J6ts_Lc5aM;kJdiu}XJ;R`1hCbO4>z56*(}T;HCk4|Yk`Ku zK_%()WcnjB5KVz+8i2bLk@?Y=3U5}5oQiRwI7BJB59Oz^OdJZyISY?rUr1ljL@90a zi5#0bgE=Q2EvO}X)ojU+N-Js;y3D4=)C2aekuc)kKD8Izb*_FMiZJTvI8Cv(3nz=hNhc6V&OR_Z* z=yD5blR1RHQz@%9RhIBXfb}GpsWuQ{Cy8((qz2%7P2j?DF>iH}90=2wJuhI}eoW*) z>`di&H?L@@=5%V6aq!CX1Wq+dRodo8H^aNz2x%G+1RFniS;ow0lx=XeqB?~h*Fr69 z(hmfZtIMz$&rEBU9)9{{;plYy{vb6`X=gifmN?3Ir`abyTCX5A2FLl^n@OF>KK-hSz$B>-4ka%@4x;~TKj$kzurnzbZ8 zv0-8*9swp!CU(Sqzy&o=)MJKbzwfs#)aS0KNQ9VzjIfn+8kRl>>j=5n7T%9G%Oa|{ z>1e_pmebaAZ0mCiIncwFBJ4fnKq?^WYZ54H-Vjy*z`h#o`wll0j5(scRN*`FrhZ1% z0V3#*2W0R{tti$~knjTHdgyFMWAdt$@B>&r)`wJ%jfQ-zgukRA*#s-`$f6x4-jN9r zDMu{rFP)+KZ88p;hPb9jxl_W=)Q)u|v@dTM*4;VTjOwjW#;SN2R> z{j~oIlC84Cwyxub@vCRC9Ei)sI}`~N=r3cnknvnLp@J&P1eyC)lS9Cd1)&icag%+1v4fa-oMO z0+!>-MvdN`TZ{YR9l;M^bD{yCj&z++Ef*EcdtZcSbgZoCqEh7g-zdGI7 zESR4LaJgV^$aE%(pW%1Nxa8#H_yNJMv7P%ZoZ|cOVV`{^lzu4~Ka?lv%X?SPo;5h9 z@vFpVUnX+J`F=s+bF$V%ZF^zbyWP~ur^N2E_8|_`J~SO84ckd^2N(I6y?@LG+xijc zu1wPSTcsEw!$AxFrE=NC0Viq$tb4EUizLG7NaUaLsbfAiGNe>?A}&%GH@XVO;yw y3_KAQIAF4rP?{%WUYI)j|Dvz{@B7Z>eYLxsj8^>^Gv+r9EY%m9ik0$~AO8y?*syy5 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..e6ea842c838d3be98032021ec9d42aecec370156 GIT binary patch literal 4536 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000000000000000000000000000000000000..e86ab54f3ddd9c7fab0741591add8ac62a9f012e GIT binary patch literal 27207 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

*#jcTgU13msy$C-!W#8*oRj zR`h;HJTN$fj$(9Kru{p}_F?T){o1?wpKU)R6$OD-Z(mkBEBO7M8roz!u)Islm3FGI zEPd%585qql16x&bxRK$BjMnoXVeDbAZeL#Nz!cE_BD7mO3D~oF_5o2%T-OA~-MKvz z^sImS%A8z*R^yA_KXDYe-n#PX&-wOG|0#zvMH~ka??n1bo^7lfH5L zO*2+liv_$>4%SA^=UgA*F1zR1ez{XqBzJwA_coaoc)r|6tu)9{Kfr>js?7Rf_C2mm z4Gv4#9+4<=W2%+~9LLex;iS!x^1OWl*9i%EHbV3OMBoyfa z5(p5G-fIXglpEjwow?ud%>8g@-VfPHHoIqc&;HKuoagL55qdgmv^Uvql97?o0@Pm` zkdcvB{`*i}Bh~!U5fdOI6PE9s&jlB#k%l$LSAJSPL4HV#rZX?x@l2(iW|V~-rnbRN!A~#D2X{I)bNBlK)C;Sq zS*a_M^a?1wZXTXu7q)UZI~zh5uF*86ds93thm#xT_=ya?J*oN@yF2EglP+2;cu*9w zRWQe3lx8WK_2;f4)DEpFQg#enA{O8qwD|RRPHOt@U=b zzGpz!zIZtllSl7b#b%!^GUJu@<0OUxF7WhFl&2d2;M&PEg`hpx0L~S_1z>kv zj}LE);!cP&cs6C~Q3`Tc&!ej;w5*CMBkvOsVRG=sLGV-3H11vQHLhHotYmpA*h;Y= z5BFlNLNaQ(d+h#h01meRC|Eyf8hs(kw_(eBH|u<43Qqc8fb^iwdaQhkxW?*Lhar zkt>1z{P`w1tc~T-R7Z<3yNLIQ+3JS@z|6&=eb#fo&cgT~e3wvOa7h5ar|Q);3ac_) zjqnecvhBQp#>OpPiZXAhgO>HH>=s71oz46PbX+{DCDSv#F`hHVb}DxqGkY-! zI^Zgsi#__I6v(>V{O4f$#A=OO);CdNfzC-0oQ_q2k;c40NH>!T{WJO}uv@oiDas15 z&Er*&8E466_c2&s!n|AXGr5&arIV>@iwL!yyF<_3q$Tr4bOqX8OHtI*s!v`Wz?pP zJ@0sr|4P`8~O1{_B~3RUrq+JhmsI%Ty{6 zfQjkxK}Ki%hzTEjmuX6y+~a;5FrL|Mf;^F4*3oV?SeV}3wSixgPAMn{ja?CQs|e{C zwc1T?CAO<}2H8mo23RW6-142sOihXZ%`{aWk&yL4mcbgHU$w6{LXc)VXx{iKr>tdX1*g;?lp zhA)>62X)d9X`Fv-B*D6LVuE#;1cu(cO1-g}n5mdWf$iEFu}fx+wW&luU3!w8L=dEWAoV2=FDlrY(_8h-xm`KY{XOR&xiI{L^Ck8m?D;%lno@1&rsGmKc za5;5e1rWmO5q|zZ=7t0zX2WuFJ8w`GU$`c`@U`DL&*t%ifqS+){zI2Rf@Mc|Rnh(c zE0f=&hO;ImU>$lb$17$VRNPXGAA2;uhb!{}rS-!mwlkYESN5g0Cfb*MQ5%ad=l?w4yMT(vXcDBkn>~H4dXk6Kj;ejs*hEpYRzB zWI8e12Um@xN20ny+V2r3E{Q~?<4;F9=9_Up7;HS-tDd4EkM|B{!!b(R$KuK~hPTKu z24&YVcLN}ZGhd(r7j;-ys#I;;_x4d?%Y;eU zkdM7bXY`i>L0b{O;HVtoPyXdx zl$A?H;0Pe+_&R@;!Y!2KEFc5D_cSB8Y89s=Vy5yLqVb>&*xn9XL4@h?9(0VnRamFO zzh+(<3zrJxx?5DUqOim79yE>wANL)#UhZo~O1<}yHe(y4nwl@T?t)+G!CQykDWah$ z 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 N0aSHfmMdF-{9nAwwuk@# literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..8c67c5626a4f7d3a7051c15947978c5c3f58ef4d GIT binary patch literal 6034 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 + + + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..407dab1 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..476fc6f0d3c7f055ac29e906d1f258fd570fc4c0 GIT binary patch literal 3044 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 U+W0K#qY;3fwvkq?# + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac3b479 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ab39a10 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/assets/icon/background.png b/assets/icon/background.png new file mode 100644 index 0000000000000000000000000000000000000000..3bbd1756734543785edb9a56fef19dbc0dec361d GIT binary patch literal 219895 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 + + + + + + + \ No newline at end of file diff --git a/assets/icon/foreground.png b/assets/icon/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..90881b4131103cc934ed450a4dffe6103f1a252c GIT binary patch literal 41337 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 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icon/icon.png b/assets/icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..409eb9c82b0f8f221c6fb255b78766c89192cb26 GIT binary patch literal 225995 zcmdpfc|6qn_y2p0eJA_AQzT@|Huf!wNGfH7B6}1SW*FJ`N{h;xq|H*1EMunFGSfWY@&3VE@dQcMr?cW;@|pQ7YBi>C%P%VC3tWW(1`;D5K+(C5{Vpuz6Y?p4|W*0Lb7uXXxC&d)+ zZBX3o^~3;T5}X4w3qs9~Z@F{w9=*x^4%s4@jb!remv^tn)!N;m+*%`Zq-pWK1)n7o zy?WT8piTX)9H;Vaun^tu<`mC#4u0L$;(K5C?j{Pt3oi^SUy2M4kYsd>%PixHch+_OGP28LIW7U(JLNz4acQ$f(y)}B3AmInMzgH*boRX(M zc#r)dWlpqU?^7jrWfhqr%2UIZ0ctkgS#{Oz!a&41ftJT+MUaQvdJjCP`Opoa-pMO( z@WF|@=YxTIF539=+Nr8^7T4hvdy)FY z0aZG;Nu0;&4gKYGLAgB!8Ee_Qu~QdvxANLN+$$?t8>H?s^io({@rF-#!&zxx8r~TZ zg9>k!ydb()mXA41sAVJ=IvW}u#21*-Nsc_c96QSEcjf99(QKEOq9?h1UZE#dCF1(h zG&y|h)mF|&sA0YD7a!;<@Ezl;WMAFBm*UEY7w?|Pcs??3XO9;<9@^`A}C| zBXb$HTMf1XZPCx)*wV2{`ZM2t8g!o8xKwoi*yFw|q&U3snB&pXEVNse9}`Rrn1npZ6zXOm)W~iMWcg{_K3wt8Bl-1DYB0)M;@vO zqzFvzU{E}G@s)`LhFGWcXa zy;938h@rqk(E}ZSod^cIHeix7xCbWyg`IRgs+(1ji>?Phs@Mx^qT!qCTurs7#SVth zMsal0mJmWJH(*S@Vd!K5BuxIg5?nyXD(uMeZMC6xqEhW2g0rE6K4)xQNGH@;Y;xpBUPThdAdEz5rYR5 zE{XqD4fh{BYvd#VZ`AdeO?vbvvsqv_l#fVE!sCe)GG6i|kHX0!qFUNhv&SYSSZ?o61n}r;?aG!gi<3uZ95@~ykaVLnX48Be==#d-xD}dix;A~% zJFn0n3Pr)b=9LZuIK;^?7|kDF7Y3?|{!}a3kib8ji8O3EwFd{ugs(yWTnE~?wk_Vo zKmoT^2pNKi%T6r!3KW26ksFZ|;7FF~VNavS0m<=_WQimZ_oQF|r8ol_CIHEU6#KyB zCeTBcaFUj}cY+2mi2ZY2nXr0h0}7PtG9uQ+lHCQMl&5}_Jn_Wy3(Fi7=n*4V>u8oC zL(422WCqpU6uR`j*K(-)1jDfk}ryYw2uA zC9h5o5|g^M#!ed}3@CItla-S9liO4%Rv{vR7HNOd8Q8exx697LxA8a{WY9|SpEayD zYymELF#MiaJq*BZYW%Z_g^k%1V5T>{;7Rv5c7y=H@5GWNPCg1>gCgz7^ucXep#efq z>?g}4pa~{{MR}J$8-Loc0<8IC%7Zh-2*BRs8CinKnc5WyFn#F%Lrg*l4+M7nM?*q1 zR1!qTX}`}P%n|%>08nW0Cu12KI?D|JD-byEpG+KX=qFcMi%q`;1MUw0cr4Gai{(FS zqW-6?xUXSZSv#-B0zwMv#{*t%hgde^$ih$TKr?tcZ;Ha1A#7jxf+kp0+Kgmw>h%B}c!BV$-W(z>D|$e>lLrvEQ5iXSEFQ zy*>XimQ0AzY-BK@z^D->5Xzwd&p+8@n~9;)7eLr_0SCtv7?27_?`#u2`YqqFgk z3lUF4H*S;Q11GwAIpgCe7T|8oAJ-uQ*EZ-(22m7}MIz=iJ`RAEog+WX{6Cb>Lje!? zAE&c7`b%>te~AF#?tfhJ82qvUXGDKimLCB4)~#JVEPz}K`2on2i4Oz-z2}ef<{O(v zLU8FO_d)!St06y^ogOa$UDij2E)&21js>{Z=QHvHkR9VAU}LI~?vD!(vnv~LX#VT$B-p&)2N1?D@BI7# z+&axGCdYV`kaY!tpMwnPmEt9rTMI0}bUisfwp0uU0Ro?;CP(y_kJ&W?N?I#uvUFVc z?JrOu{Rhj>|6Oiln1e0u{*RWYei|+$>IOT;Q?a)ZcoRYtIPt{L!-f9cR{n-B*c2~G z>`Ean*2;wcZjpAw7dFoFW~qFm6X3d$GC8`=j!6dWBsI6W48#;J zh3}6k{C9ZrA8rSK$CV|_%;TTK0QgZdbXmOL6b`D5lQEm6$nz$;rml8WWgZK8@Z-wkb#?ykmF-p3_<{xcgB8yAa0{~ zMEze#n(b*(KY|C4@0q^44TMK+%)1H1^-@n_i=IeEY?Tt1yI}Zb;Y4u5NWP|!=eLIo zH+r8QthXg@y9kujqFI2(?%&?OBM+P$z08R}cwQKYPx%+6AZ9wuH~o5gm~1k*M?wb-z`Tb1dU=?9Qo+?a z1knA|;MWUyWFfhx`n{lk!0Ajp_{Z?;;bF2NUWa90u0S%co+$d|R_K4pyPgv|?)+Cr ztG;dkfTLT)4u^Rok#_MBa4du+@U)t2x602Kqshl{9@lrmfNoAQGz`U7fq^0gOMku= zO+LAeU#q507?{rg`RqLTfd4xrB}*t!=uC#;2xapG)mM|BZz=zm`1qe*_4^}0QX3b7 zw}QOL@u8{LjIaQY-~RmV3i2V4UaXW1AnvRFd}f$@NY*1g9vpZJ59w7zhQJ?Dqe2Pd zV-p$TBTAtxs)Yqu{N+P2^6C_tD*y;79DpNRmV;@Gy(wgaOaD0QkX3GDH7*M-=h0p8)thEi!{heHmC<7nfuWSN(d`4?=$77~$CsUKise!-p2}wh)v`q@?~^Y_!G-BY6MY<9(HX z$k-7WK5$44Hw*G)*35foa>EfU6G0yV0CGm;1|zjTJm=>C7$7l9y-HrppLSR+z9k56 z8Vb-FTau;QaNs9teaE0=X`9tGP{Er@CbPOG0>j_vqQyQ+1Nc>tAx1hY2{91AVsZEg z*-`&UMc$;Z->^uEBRBdVF{mIl8WRK-Zf&k)i6t)a!T^3d+Rm9Aqmrfe3wYg|%!-?@ ztR&N64FV@em+e010RzYqMQ)#XPlF`OoGq9L&VZ89RFeo}qycTUlSSj}_ux)2S-MR6 zA(^_&-~1z)x=c+597iUr!5_vb*3i1k@e^D($uvv%QR2xnOSgGtlBvsr^;V!{S$*E{ z!S2J6t7-NP;7x0?)aa_5xWO+skR>5rrapW(lst*!JX87QH8RcC{L>>#WJ<9Hhq&9w zHLn>s;Mx!b#8dnr&>VkEy+09WqQE}1i<;xL)IH_PlKxMqeMY5?6>9T271T8Cb0c*| z3=%SSnY~L@G&=Zbk63(^0re-GBKzoaBK+5%B?jrN17JzTku&j(W{3Y3fk1)%5iGa*Gf10r!)UQ9kl@0 z(BJ)1sLeB-o+GZu>eNl08iR$W0D(%GmsQEq?R6f{W}py9QnUq z(Qr}_(8n@4E8XyEjw9SZ3+^=&1PODTyB>)X*y1V6&O;g=n#nR9ZE?6?(~FH^i&r>K z^`e!T>vmS#;K5x(w`@`$yI6GC-Mc_LI-DG)iHNTc0`ti1b+n+%Llom&x%TA|NmI3+ zJp#O$$#VAG+|^12Vv-l4Umw|$c&y&_ctJ#T>W-*4hbqjVIlakpCk}qHLbDA{?q(kt zdivzq@k4iNQ#=;Tr`v4lTne^(Ok@|n-J-WsA-21yQue&D4T@qHoO{49aQityjo*Az z!}YVons##ziG(B62;iJ>@3kZ&-65CmhzKGj-X3)*Gvt^Hofwe zw2@}uoJ&o48TO(z=jJTKYg{&dTH^9HHj;)HUhK&IG-Aoe(;w-Q(t9F`A`xqhNX1YO zw(hn2cz`p}A!pwaM4q{b&Gr47+~@Au(H#i4u1}mfbVd0pABQi+Q;bx3`sV5O?=J|a z=6+om1$P;KE@}8bA34)6yao)}YJ3*1F-2joI&_Fy-%v^At{D5YdX0sa&@5srs3Jp7xy^C+EE9r{v zFO-)Lb+t7zmtnirU@OoT{rrtB9h;;-^X;cW=c$cLMfZG{Fo&TlQ=D$#9=_gBHKs9gzv2OUX4&zGkzJk_Et|2d6J*_d%BZ~2(G;wd*O)B1RD7-KE zSOqS^ZVLR{Jm#}wL*e0AIT7fy$qhCb4=;gxP`h4`TNu`SyrB&GONb(bbL~L;6$i~r2EcB4zIW7F$zM=yk+xE zrtuT}r{)Ie`t*!7n^_<9qBb864>-TIAq7I;5`NNePBv%D9{m{gt$8Cs#twOT=Fu`& z{7w5C(v=m;nOek3-!oGQ%q8F@hMB6;@7(Qtv_;^F@q>E2#YNpbjgnLH^at;;Kcviw z7VLeh6(#-cO%#bPg<20U7KErs0J z6spS$jjCY*RQ|3Gt3UX1BULLr72Z(6fKWgo7bZZ4rfIt20FlIh%`~yYG#%@yo@?WA z{U77 zZTh5lUZFu0ih}(I*C~E0ebjtZ#h^xL0I=@;cfda;tut&Pd7bH+poEYEf@J@Fg#c5| zG$OJL8CqtE`B&fs1sk5Nt|Ebtj780lqQCPW-r^DRU-4fvs*MF0E%N=x)r>^^1HGdO z-_b2ayq$Ap`2R$>j^IT83ylZ#VBruJGDL9J^g7A@m&)AVmDl|uZ885yzk=grd}IDy zsfP%jT5bd&^Wvqy7U%vZDd?h#c7N3lK#})(`2V1a=v8a(9{-gq^P})dkbhS$CbF+b znRo~i)Z+e9p8FeCnL<^vtWUJf&8AS^6iI?@0Imu7ue%L(CK;R-35@+mZYvB=;}Qq8 zaM4)taADC+zo?JTAi{fZ{_&z4p|KjrpK9d&T##OUgr9H@Av{rWaW>BsHyHHj{@&qc zl{1oy#|kKw-d=g+Z{2HDb=u!>q=EJymR7dVg`kXW6A2>dojRHLN#tJHhg2{Wx34 z#`0g#k3npCR|9^tM@0A^;fk{9lFInU#U)2ZdCcq0xe32SCzf5nMH*Ka z+EyH68;GrBmJUWFUQOcQv$Q4VoS|*@+$-CJaQxoeO212!P>@9wdyKyJbCVT(bw0wy`=uqH%F8$oI6dMxWDuMN;aOsZ3vtk}B z8=!Tz3oJi?G;O8p$hgh*&l~FB+}xOd83yo_L;upvGJheILK>9zzte5T#U#?o@zvH>uh;%sOPxhS)E z>^S8=?@u!xF4fa4^V@=7uI7UQ@dQf1i~Ub~)PGHbvBSI(_Y?Jf@!b80m?bYKcNWhD z&AXon9fYC5EkRd= zkDV>N({@jk`|bVXR?h@(x-+-Ha*x}_njcHEXAcXwDMIhY8NJzx;#AD$%h3zr;mXW+ zp0VaDZZX>PIf-KvMA9l+Hb-*Sq zEoF1xDK&SV2l1KEk9@C&TwIkmMO?aYE#I_E;Qd3N<0Yl(ND(_R4JYQ1C;|c7J21Ma ziC8`Ri3Z<#)PDzT`Kn`C$cKq4E<3Tq7Yl}Jx$fK5zA@Ildy)_#h0USi*>W|isaPi8 z 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icon/make_icons.js b/assets/icon/make_icons.js new file mode 100644 index 0000000..f07ce80 --- /dev/null +++ b/assets/icon/make_icons.js @@ -0,0 +1,123 @@ +// Generates the Tagesbyte app icon sources (SVG) and PNG renders. +// Usage: npm install @resvg/resvg-js@2.6.2 && node make_icons.js assets/icon +// then: dart run flutter_launcher_icons +const fs = require('fs'); +const path = require('path'); +const { Resvg } = require('@resvg/resvg-js'); + +const out = process.argv[2]; +fs.mkdirSync(out, { recursive: true }); + +const BLUE = '#3355DD'; +const LINE = '#C9D3F7'; +const SPARK = '#FFB52E'; + +// Design space 1000x1000. A news page with a bite out of its top-right corner. +const page = { x: 290, y: 230, w: 420, h: 540, r: 52 }; +const corner = { x: page.x + page.w, y: page.y }; +// Two overlapping circles: together they remove the corner and leave a small +// cusp between them, which reads as a bite mark. +const biteCircles = [112, 158].map((deg) => { + const a = (deg * Math.PI) / 180; + return { cx: corner.x + 48 * Math.cos(a), cy: corner.y + 48 * Math.sin(a), r: 88 }; +}); + +const sparkle = (cx, cy, s) => + `M${cx},${cy - s} C${cx + s * 0.12},${cy - s * 0.12} ${cx + s * 0.12},${cy - s * 0.12} ${cx + s},${cy} ` + + `C${cx + s * 0.12},${cy + s * 0.12} ${cx + s * 0.12},${cy + s * 0.12} ${cx},${cy + s} ` + + `C${cx - s * 0.12},${cy + s * 0.12} ${cx - s * 0.12},${cy + s * 0.12} ${cx - s},${cy} ` + + `C${cx - s * 0.12},${cy - s * 0.12} ${cx - s * 0.12},${cy - s * 0.12} ${cx},${cy - s} Z`; + +const pageRect = (fill, extra = '') => + ``; + +const biteMask = (id) => ` + + + ${biteCircles.map((c) => ``).join('')} + `; + +// Headline bar, text lines and the AI sparkle on the page. +const contentShapes = (headline, line, spark) => ` + + + + + + `; + +function foreground(scale) { + const t = `translate(500 500) scale(${scale}) translate(-500 -500)`; + return ` + + ${biteMask('bite')} + + + + + + + + ${pageRect('#0B1460')} + + ${pageRect('#FFFFFF')} + ${contentShapes(BLUE, LINE, SPARK)} + `; +} + +const background = ` + + + + + + + `; + +const svg = (body) => + `${body}`; + +// Monochrome (Android 13 themed icons): page silhouette, content cut out. +const monochrome = svg(` + + ${biteMask('bite')} + + + ${contentShapes('#000', '#000', '#000')} + + + + ${pageRect('#FFFFFF')} + `); + +const files = { + // Full-bleed icon for iOS, web and Windows (the OS rounds the corners). + 'icon.svg': svg(background + foreground(1.1)), + // Android adaptive icon layers: content kept inside the 66dp safe zone. + 'foreground.svg': svg(foreground(0.9)), + 'background.svg': svg(background), + 'monochrome.svg': monochrome, +}; + +for (const [name, content] of Object.entries(files)) { + fs.writeFileSync(path.join(out, name), content); + const png = new Resvg(content, { fitTo: { mode: 'width', value: 1024 } }).render().asPng(); + fs.writeFileSync(path.join(out, name.replace('.svg', '.png')), png); + console.log('wrote', name, 'and', name.replace('.svg', '.png')); +} + +// Preview: the full icon at launcher size plus a circle-masked adaptive icon. +const preview = svg(` + + ${background}${foreground(0.9)}`); +fs.writeFileSync(path.join(out, '_preview_adaptive.png'), + new Resvg(preview, { fitTo: { mode: 'width', value: 512 } }).render().asPng()); + +// Monochrome preview on a dark background (the real file is transparent), and +// the full icon at a real launcher size to check it still reads when small. +const inner = (s) => s.replace(/^]*>/, '').replace(/<\/svg>$/, ''); +fs.writeFileSync(path.join(out, '_preview_monochrome.png'), + new Resvg(svg('' + inner(monochrome)), + { fitTo: { mode: 'width', value: 256 } }).render().asPng()); +fs.writeFileSync(path.join(out, '_preview_small.png'), + new Resvg(files['icon.svg'], { fitTo: { mode: 'width', value: 96 } }).render().asPng()); diff --git a/assets/icon/monochrome.png b/assets/icon/monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..846ff082f2594069d026bcca04dd0a62990c9305 GIT binary patch literal 15573 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 SZZrJF-8+4E+};-a)qeoX-hd?l literal 0 HcmV?d00001 diff --git a/assets/icon/monochrome.svg b/assets/icon/monochrome.svg new file mode 100644 index 0000000..58fc3ab --- /dev/null +++ b/assets/icon/monochrome.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 0000000..4032de0 --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,51 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the codebase. + +## Before exploring, read these + +- **`GLOSSARY.md`** at the repo root, or +- **`GLOSSARY-MAP.md`** at the repo root if it exists: it points at one `GLOSSARY.md` per context. Read each one relevant to the topic. +- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src//docs/adr/` for context-scoped decisions. + +If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. + +## File structure + +Single-context repo (most repos): + +``` +/ +├── GLOSSARY.md +├── docs/adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +Multi-context repo (presence of `GLOSSARY-MAP.md` at the root): + +``` +/ +├── GLOSSARY-MAP.md +├── docs/adr/ ← system-wide decisions +└── src/ + ├── ordering/ + │ ├── GLOSSARY.md + │ └── docs/adr/ ← context-specific decisions + └── billing/ + ├── GLOSSARY.md + └── docs/adr/ +``` + +## Use the glossary's vocabulary + +When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `GLOSSARY.md`. Don't drift to synonyms the glossary explicitly avoids. + +If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`). + +## Flag ADR conflicts + +If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 0000000..0508e06 --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,40 @@ +# Issue tracker: Gitea + +Issues and specs for this repo live as issues on the self-hosted Gitea instance at https://tea.zendric.de, repo `Cedric/TagesByte`. Use the `tea` CLI for all operations. + +`tea` infers the repo from the `origin` remote (`ssh://git@tea.zendric.de:2223/Cedric/TagesByte.git`). If inference fails, add `--repo Cedric/TagesByte`. + +## Conventions + +- **Create an issue**: `tea issues create --title "..." --description "..."`. For multi-line bodies, pipe a heredoc into `--description-file -`. +- **Read an issue**: `tea api repos/{owner}/{repo}/issues/` for the issue as JSON, and `tea api repos/{owner}/{repo}/issues//comments` for its comments. +- **List issues**: `tea issues list --state open --fields index,title,body,labels -o json`, with `--labels "..."` / `--state` filters as needed. +- **Make an issue a child of a parent**: Gitea has no sub-issues. Put `Part of #` at the top of the child body, and add `- [ ] #` to a task list in the parent body. +- **Comment on an issue**: `tea comment "..."` +- **Apply / remove labels**: `tea issues edit --add-labels "..."` / `--remove-labels "..."` +- **Close**: `tea comment "..."`, then `tea issues close `. + +## Pull requests as a triage surface + +**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature requests; `/triage` reads this flag.)_ + +When set to `yes`, PRs run through the same labels and states as issues, using `tea pulls` (`tea pulls list`, `tea pulls `) and `tea api repos/{owner}/{repo}/pulls/.diff` for the diff. Gitea shares one number space across issues and PRs. + +## When a skill says "publish to the issue tracker" + +Create a Gitea issue. + +## When a skill says "fetch the relevant ticket" + +Read it as in **Read an issue** above. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets. + +- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `tea issues create --labels wayfinder:map ...`. +- **Child ticket**: linked to the map as in **Make an issue a child of a parent**. Labels: `wayfinder:` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev. +- **Blocking**: Gitea's native issue dependencies. Add an edge with `tea api -X POST repos/{owner}/{repo}/issues//dependencies -F index= -f owner=Cedric -f repo=TagesByte`. List a ticket's blockers with `tea api repos/{owner}/{repo}/issues//dependencies`. A ticket is unblocked when every blocker is closed. +- **Frontier query**: list the map's open children (from its task list), drop any with an open blocker or an assignee; first in map order wins. +- **Claim**: `tea issues edit --add-assignees Cedric`, the session's first write. +- **Resolve**: `tea comment ""`, then `tea issues close `, then append a context pointer (gist + link) to the map's Decisions-so-far. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 0000000..5276464 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,15 @@ +# Triage Labels + +The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker. + +| Label in mattpocock/skills | Label in our tracker | Meaning | +| -------------------------- | -------------------- | ---------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | +| `needs-info` | `needs-info` | Waiting on reporter for more information | +| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | +| `ready-for-human` | `ready-for-human` | Requires human implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table. + +Edit the "Label in our tracker" column to match whatever vocabulary you actually use. diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..b45f3c3 --- /dev/null +++ b/instructions.md @@ -0,0 +1,13 @@ +Hey i want to have an app that gives me daily tech news. +Please ask me questions if something is unclear/could be improved + +The app should be build using flutter. +I currently already have a n8n workflow to get daily tech news, and it send me them via discord. +I want to replace this. + +For this we need to get data from tech newspapers etc. Please provide me a list of sources. (language preferably german/english could be fine aswell) + +It would be amazing if i could integrate some ai to summarize the articles. +This is what my n8n does already currently( maybe we can depend on this workflow for the app). + +Can you develop me an inital prototype diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..02a74b0 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.dailytechnews.dailyTechNews; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = de.dailytechnews.dailyTechNews.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = de.dailytechnews.dailyTechNews.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = de.dailytechnews.dailyTechNews.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.dailytechnews.dailyTechNews; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.dailytechnews.dailyTechNews; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d0d98aa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..9d48ad3057db5c7df0d26b8b07c6e52f4346770e GIT binary patch literal 65400 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�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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..f1127474365e6ac01987809e9ebb6c4b472863e8 GIT binary patch literal 511 zcmV=PmDnM6d^ zGfC)p?qUueK{|=VnMTg%*m^$B7`Wr+?qmC~O70OJAIujm=d)AxU@`=rF*DttP8zj_ zPP$>MAjM30|I|D=dv)Qpt`Guze*ZqU?yZv=|3~Sk&|;uQ2iA zTG+}z0EKNMHyM^dwce78M9dVpbkIh8A`zQJ7BgZIpHBejvkW3=Hyo*pNC*L-`9m*n z+=pT-ow7yN#GSB%B9WC719+P?O&HH0JRUoB(d~{j}p^y!R=~X1t?uucCEIaOe zan%)J_1@5qB3baRD6=XIDn)4|$*{YUsJZHH`U3zcnOJ+3`O-E3l#Hnv(psTJE=|9a7bh_~Lfco7jjsGx!e#UBJO1wDB8q&F`@J$Mnt zL+MQqqG**~iin6+{6WRmT5Fn^?2Ke*cXqQgvq?qmBqY0=-FfnT-#hPo_WpA&_XdJ5 z|Dblrlq>Qc7G6Q*C(e{B@*Wmm0k;>0%DD&r|I`||St^pL30E{!KjQx!tN6uU6e{P; zUJPpOORBmtHJnQ+{{pEDfvSya;+*xbvkvWPX=|kbNB!X9gX8Ai>5+T0@TX`@6{=2( zb&2C%JheQRrAQo0Tfvx12u-eQl%lFSq%y`xV&Z`Zn z0QxuN_v~!DJvsvqs8v&pVwK_a{c`_FQ5){T-G#Gr>C3m!eZ=(&WML zS|iveK}(Goh-qI7*arp+*(Eeu;;ox9iOKebsi8tubR0Iw&e!4EuIZMF-i$L5PdHpv;FE_bE(dejce0E zzziU2tQ^unOdU+EJpPQmT`AX1WO9betd&{npZVJqv{aGmguaObetJt~UMg!8Zrdp2 zz5LERNAW#!yP~-cR2`>kRUPS@5U!@rQ`MOtnp8U}Qs}j6$jKWhe?`6Ug&%7gTqrla%LdCpHgZ{v%q~2N4IU)=^EW4tG%2yni?ar1J8NiRJ5)&aPg>P4=hN>%Hq-eQ_7vh$b5PaNr8-ky$Qbjjf0 z)8EQhu1!pTuUJ+Z25QW!+`?IcmwXC@P(mHc$^Le2`Yd91D%6O9=&3;$ z?r$_=W%|_&Jy;glci6_%dQ~g2CiIBLd(3L8>p((PYpD8u_^(g{RsR81x6?IG^}O0t e*MAnP7{38%4s%w7a)ass00006!EdB zB1#1XU38&4cOp?&CKRDZ!ocJ*TxXrmfm>c$l7>f+ozS4f;us* zCoxYhD#Yq<<#$q>Lxq?z&@&VJaQ)ACxcu#$CU94xT#?ux@>Gc#qt|!fLx)TI`gDyT zG(r60JQxvn@`Jx-{;Z2aknv5(?OtZllML-Uig_w!t5?L>c z6^v0zt;Zg$WFQ8Bj%_$QM+qUoAPA)D6&yxaBzCFI#{NCI48#C%`@!<)FMl&cNWb$! zA6!=_W{g_YL?U(BWO?B3&ab|ipI>w~C1fTRKAoIFoV%1pHlC^!3)+d}d!K)Gdb#SV z$VTj1S}Esw#?FY`l%avbsgr|GAD>)Wwg502vF+2!S=#xkjK?^}*!!O^d^=K=hd57i zIehBj?e`oiIj7w_jW=K1d+fFI^9vTYwmPx1L{}r`{{+qSAB|7WEUFmzjgK#LXxrX{ zh12g0efjm}?|xjG`=_4E;rv2Fk(l|?crr0Cuc+{(W1ChgV09(g$9H#!15UZ54Sln6iQ7*VjDY@&LqUiX*{?&Ak1A`rQ;Xs(T%8SaG*c0 zYp6nOYo`>5h~M$Vfi#{#%&QSkR@dm!7iMSXOexA82Mcc<>r%9|8~JQYCliY_otHfQl>_z z9ktPdM;=VVW3d**;5)qrkYVPaPsbDUDYKZvuSS{Gzx7zmM5n1fDfyWdc#55(6Mv7W}2BSt$~sATN+~1czD;Tk9tEr01$%x zz3ImZS0K#~vnbPO^0(s(j)H00GA?Cj)P zVsa_AqD`PF-=M z1Tmnn3u>3_gZ5tgSm&*-TNWWG5vLmU0zY#^*PA9(7o!5*cR_Uu2A>SwiMU1_ak^2j zf9R22dheB86)kfjaf==?vRl1k+P-APH{Xd;=nGx26^4R`?Ct*AI?^M`*9cRL|6bkK z7dsM2DZfoDiT*-?IMO4wCT{At%dn3=n>ut$N2!=`;2V7RXWf&4H?|}W_K0!B zuAvdh`Pr+FKlAVbK7^ovP zfl(${1$t1G_&@kGgJS~(jS$UlVf+hXiCgxFErWd1*qZGo8l#ja%iwPIhXI-K#CJgV z2T~Brd&Jmc#d#MJ)EJT&z0yQ$cT}O?Bxqr!QE#+Ij4!W5G8dal>_zv_ZBAt@9*R7G z?l;Rq9PJTRh?A8n44D6)a-UCc6fvY%u5UQ8^4ee;-&(}JziuTG!)77&l8Xwlq*C1& rVn|BkEtR$?v!u~FtILJgiZ}iTQk@L#GSKvK00000NkvXXu0mjf<+B(C literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..9f31ab0b922b7010211892a6ef4292e0d4405463 GIT binary patch literal 732 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..9674f793920f8c40d73cd35ac25a1761c2854bb5 GIT binary patch literal 1336 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 P00000NkvXXu0mjfu7u>l literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..842813e6b49f71bc58be813844732f30c6d9d646 GIT binary patch literal 3505 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..14aa58e0fa263b8425e4520e67d68b117613ca44 GIT binary patch literal 4089 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..57a1acafc4d55008a76d60ac5748dc8b3a92f1f8 GIT binary patch literal 7590 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..aec1186de996c025d757430ff3b30054b6b2eb4c GIT binary patch literal 6921 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

RV ze+O-{vRs?k`!~{dXlh#0yuB<^I$A;qmHLoFpL|`Ak&mQ+ng4ADAQ5|k_3r7T4dYH>O4O$L*N}frFtW8+t(+rqjh~x5-cwnb3VCfIyYsLx zXZwqngJm+M!qRKI&J_%0kK>hcwDap*kk+HUSa8O%Q8L9}y(AVh)7KQHtwv%Z+t}@e zHKA~FK}fGVbG-|dDp}9r)>XF=HU}RBS<>`o83~UF<)v{=w*bdz50UlA@B^nYT)cW9 zG9u8>HHVh!%McQzkA)~cO5(AAc_)%u%mBxAsP?F2o{+W07ITlwmsiD zX>kp0Z@&_c|G2jpAIpIH(a+LAUn~fb4!@N@;9s-M)pDkay1_V~Gspsz6UhU>^o(8pZoeMnIouZ4r!x2Q!pujT^RiXTZIaxx-Yww z!u%gp!C39^KKWA|M)Z1>GWayy-HpGp9)5G3lg{>gBhE@6y7iQd;OOH1w`H*LByu(5 zvMX4?ydc6F_A7`J_-t7=?4ens$)%BW<=V(7ZmGd%|4OMDDvPNM4fu5w&fd9~Z&U4V zO@;7{m9!L9mQ#aoC_?7SSt-Dv}*8vB? zK{s8rF`BdnuQ+8GN#><*Cs3lXF+u5(&(w89c4*~I=7#z^wcR|tMks{1318Re^{L?B z$GM|#w@qFT*<&82OshrEYX~x;j-wg-)070NwG>kyupAfo(oA#(5-8hnlwJ<(Jk%)_ zzg4pY55}dj;_dZEj+t78?lj5Xz{N{$-3(hRn&eEkG+RFcm4^gcHGe<0uQ<4~9x9>S zM9!aDaaM@jtrDuT2T2a4a)}q$Y$ljSUtTe&M*iAELQUx`#rTf&dmvWm{Wk7_8RG}w zz^fF>MDjkzs>a6^7z+4a|`Q6rr3rmK7JK{qC2LE3gjb% zYo`Ts@rU0BZ|3XWQJt4_eA`!OH}QamQz4YTr&Y&c?Kw2IJt5H5_)$bdSAe z`?<&H-fQFMz=<$zFNLQ>vzTbUY~i~J*FtZ#yr}wK#06&@X#lq>cWcyW(^8+6k8`>G z#|k}fjgDoT0IM?k`|L?N%Ch@VrSzXIDmFRfV>578Y5KuFO7W*PEO&)}($#%&_=PXh{wi&>9?q;FYNnrI?nEdo(s$tM^MXm= z5TC+T0q;0D!Xk+^1w4yHfH63I`pRdxru&sGjq}gdXXHM-eYKs2WM1uKDwdOxpXoB$SKfs&-o5R!f+FS0>T$S?1?F@nfE<+~S%X&LGEl_UvYx>I zTbj|`o8GlD!s+fm^X7Z-)0jGVmpzdwXhH=Du!5B@Dvl_6x5N2&gXYIwUqw|DFLBx{5K$T1^#r=Q? zL`XyDL1*c$k2xN#`-L7x80w`M9W(xVQ&9Bl72Rc{3AOrB`)USH(OFXAI>c<2Iq;h1 zzhIS6frNpG=z@jYK}Yo2SnM`Kb*jGFB;RvooIfSzvP%>s95D8@isFO%Hgue z_lla0aqmG+$PDq~cVakLl^d=dYg6v5a4W0BU+_i&?&%-J+;W+Oi5p4DE!Uz`pGFr5 z05_<=5R1df|D*s;Y~H8;qk@}Kbly3uoiv+(0k7h?(AF5V4*rLdyH+E36he764Jvd-OTl06xs@b}O#4 zClnNCvF?mzp`f4HTK?Y!e)h5FCAUj0DB~e~7_`OEccOGdSzCXI(zYAgy1nAU(*)_N JRjF8o{SP2egb4ru literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..8bed01633dd49f314947f1295bfa50bdf75de907 GIT binary patch literal 2548 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..7c3df04 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Tagesbyte + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Tagesbyte + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + + + UIRequiresFullScreen + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/data/default_sources.dart b/lib/data/default_sources.dart new file mode 100644 index 0000000..fe1e6f5 --- /dev/null +++ b/lib/data/default_sources.dart @@ -0,0 +1,187 @@ +import '../models/news_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 = [ + // --- German ------------------------------------------------------------- + NewsSource( + id: 'heise', + name: 'heise online', + feedUrl: 'https://www.heise.de/rss/heise-atom.xml', + homepage: 'https://www.heise.de', + language: SourceLanguage.de, + description: 'IT-Nachrichten, breit aufgestellt (hohes Volumen)', + enabledByDefault: true, + ), + NewsSource( + id: 'golem', + name: 'Golem.de', + feedUrl: 'https://rss.golem.de/rss.php?feed=ATOM1.0', + homepage: 'https://www.golem.de', + language: SourceLanguage.de, + description: 'IT-News für Profis', + enabledByDefault: true, + ), + NewsSource( + id: 't3n', + name: 't3n', + feedUrl: 'https://t3n.de/rss.xml', + homepage: 'https://t3n.de', + language: SourceLanguage.de, + description: 'Digitales Business, Startups, KI', + enabledByDefault: true, + checkPaywallOnPage: true, + ), + NewsSource( + id: 'computerbase', + name: 'ComputerBase', + feedUrl: 'https://www.computerbase.de/rss/news.xml', + homepage: 'https://www.computerbase.de', + language: SourceLanguage.de, + description: 'Hardware, Software, Gaming', + enabledByDefault: true, + ), + NewsSource( + id: 'spiegel-netzwelt', + name: 'SPIEGEL Netzwelt', + feedUrl: 'https://www.spiegel.de/netzwelt/index.rss', + homepage: 'https://www.spiegel.de/netzwelt/', + language: SourceLanguage.de, + description: 'Digitales aus Sicht eines Nachrichtenmagazins', + checkPaywallOnPage: true, + ), + NewsSource( + id: 'zeit-digital', + name: 'ZEIT Digital', + feedUrl: 'https://newsfeed.zeit.de/digital/index', + homepage: 'https://www.zeit.de/digital/', + language: SourceLanguage.de, + description: 'Digitalpolitik und Gesellschaft', + ), + NewsSource( + id: 'netzpolitik', + name: 'netzpolitik.org', + feedUrl: 'https://netzpolitik.org/feed/', + homepage: 'https://netzpolitik.org', + language: SourceLanguage.de, + description: 'Digitale Freiheitsrechte, Datenschutz', + ), + NewsSource( + id: 'heise-security', + name: 'heise Security', + feedUrl: 'https://www.heise.de/security/rss/news-atom.xml', + homepage: 'https://www.heise.de/security/', + language: SourceLanguage.de, + description: 'Sicherheitslücken und Security-News', + ), + NewsSource( + id: 'heise-developer', + name: 'heise Developer', + feedUrl: 'https://www.heise.de/developer/rss/news-atom.xml', + homepage: 'https://www.heise.de/developer/', + language: SourceLanguage.de, + description: 'Softwareentwicklung', + ), + NewsSource( + id: 'caschy', + name: "Caschys Blog", + feedUrl: 'https://stadt-bremerhaven.de/feed/', + homepage: 'https://stadt-bremerhaven.de', + language: SourceLanguage.de, + description: 'Consumer-Tech, Smartphones, Streaming', + ), + NewsSource( + id: 'derstandard-web', + name: 'derStandard Web', + feedUrl: 'https://www.derstandard.at/rss/web', + homepage: 'https://www.derstandard.at/web', + language: SourceLanguage.de, + description: 'Tech-Ressort der österreichischen Tageszeitung', + ), + NewsSource( + id: 'borncity', + name: 'Borns IT- und Windows-Blog', + feedUrl: 'https://www.borncity.com/blog/feed/', + homepage: 'https://www.borncity.com/blog/', + language: SourceLanguage.de, + description: 'Windows, Patches, Admin-Themen', + ), + + // --- English ------------------------------------------------------------ + NewsSource( + id: 'verge', + name: 'The Verge', + feedUrl: 'https://www.theverge.com/rss/index.xml', + homepage: 'https://www.theverge.com', + language: SourceLanguage.en, + description: 'Consumer tech, culture, policy', + enabledByDefault: true, + ), + NewsSource( + id: 'arstechnica', + name: 'Ars Technica', + feedUrl: 'https://feeds.arstechnica.com/arstechnica/index', + homepage: 'https://arstechnica.com', + language: SourceLanguage.en, + description: 'In-depth tech and science', + enabledByDefault: true, + ), + NewsSource( + id: 'techcrunch', + name: 'TechCrunch', + feedUrl: 'https://techcrunch.com/feed/', + homepage: 'https://techcrunch.com', + language: SourceLanguage.en, + description: 'Startups, funding, big tech', + enabledByDefault: true, + ), + NewsSource( + id: 'hackernews', + name: 'Hacker News', + feedUrl: 'https://news.ycombinator.com/rss', + homepage: 'https://news.ycombinator.com', + language: SourceLanguage.en, + description: 'Front page links, ranked by readers', + enabledByDefault: true, + ), + NewsSource( + id: 'mit-tr', + name: 'MIT Technology Review', + feedUrl: 'https://www.technologyreview.com/feed/', + homepage: 'https://www.technologyreview.com', + language: SourceLanguage.en, + description: 'AI, biotech, climate tech', + ), + NewsSource( + id: 'wired', + name: 'WIRED', + feedUrl: 'https://www.wired.com/feed/rss', + homepage: 'https://www.wired.com', + language: SourceLanguage.en, + description: 'Tech, science and culture', + ), + NewsSource( + id: 'engadget', + name: 'Engadget', + feedUrl: 'https://www.engadget.com/rss.xml', + homepage: 'https://www.engadget.com', + language: SourceLanguage.en, + description: 'Gadgets and consumer electronics', + ), + NewsSource( + id: 'theregister', + name: 'The Register', + feedUrl: 'https://www.theregister.com/headlines.atom', + homepage: 'https://www.theregister.com', + language: SourceLanguage.en, + description: 'Enterprise IT with attitude', + ), + NewsSource( + id: 'bleepingcomputer', + name: 'BleepingComputer', + feedUrl: 'https://www.bleepingcomputer.com/feed/', + homepage: 'https://www.bleepingcomputer.com', + language: SourceLanguage.en, + description: 'Security and malware news', + ), +]; diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..ee61a5f --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'services/storage.dart'; +import 'state/app_state.dart'; +import 'ui/common.dart'; +import 'ui/home_shell.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + // Portrait only; also set in AndroidManifest.xml and Info.plist. + await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + final state = AppState(await Storage.open()); + runApp(DailyTechNewsApp(state: state)); + state.init(); +} + +class DailyTechNewsApp extends StatelessWidget { + const DailyTechNewsApp({super.key, required this.state}); + + final AppState state; + + static const _seed = Color(0xFF3355DD); + + @override + Widget build(BuildContext context) { + ThemeData theme(Brightness brightness) => ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: _seed, brightness: brightness), + cardTheme: const CardThemeData(margin: EdgeInsets.zero), + ); + + // Rebuild when the theme setting changes; everything else listens itself. + return ListenableBuilder( + listenable: state, + builder: (context, _) => MaterialApp( + title: appName, + debugShowCheckedModeBanner: false, + theme: theme(Brightness.light), + darkTheme: theme(Brightness.dark), + themeMode: state.settings.themeMode, + home: HomeShell(state: state), + ), + ); + } +} diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart new file mode 100644 index 0000000..42514a8 --- /dev/null +++ b/lib/models/app_settings.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart' show ThemeMode; + +import 'news_source.dart'; + +enum AiProvider { + none('Off'), + claude('Claude API'), + n8n('n8n webhook'); + + const AiProvider(this.label); + + final String label; +} + +const claudeModels = { + 'claude-opus-5-5': 'Claude Opus 5.5 (best quality)', + 'claude-sonnet-5-5': 'Claude Sonnet 5.5 (balanced)', + 'claude-haiku-5-5': 'Claude Haiku 5.5 (cheapest, fastest)', +}; + +class AppSettings { + const AppSettings({ + this.aiProvider = AiProvider.none, + this.claudeApiKey = '', + this.claudeModel = 'claude-opus-5-5', + this.n8nWebhookUrl = '', + this.summaryLanguage = SourceLanguage.de, + this.maxAgeHours = 24, + this.corsProxy = '', + this.hidePaid = true, + this.hideSponsored = true, + this.blockAds = true, + this.hideCookieBanners = true, + this.acceptConsentWalls = true, + this.themeMode = ThemeMode.system, + }); + + final AiProvider aiProvider; + final String claudeApiKey; + final String claudeModel; + final String n8nWebhookUrl; + final SourceLanguage summaryLanguage; + final int maxAgeHours; + + /// Web only: prefix for feed/article URLs to get around browser CORS. + final String corsProxy; + + /// Hide paywalled articles (heise+, SPIEGEL+, Golem Plus, t3n Pro). + final bool hidePaid; + + /// Hide sponsored posts ("Anzeige:"). + final bool hideSponsored; + + /// Original-page view: block ad/tracker requests and hide ad slots. + final bool blockAds; + + /// Original-page view: block consent managers and hide their banners. + final bool hideCookieBanners; + + /// Accept consent on sites that only show articles after accepting (Golem). + final bool acceptConsentWalls; + + /// Light, dark, or follow the phone's setting. + final ThemeMode themeMode; + + bool get aiConfigured => switch (aiProvider) { + AiProvider.none => false, + AiProvider.claude => claudeApiKey.isNotEmpty, + AiProvider.n8n => n8nWebhookUrl.startsWith('http'), + }; + + AppSettings copyWith({ + AiProvider? aiProvider, + String? claudeApiKey, + String? claudeModel, + String? n8nWebhookUrl, + SourceLanguage? summaryLanguage, + int? maxAgeHours, + String? corsProxy, + bool? hidePaid, + bool? hideSponsored, + bool? blockAds, + bool? hideCookieBanners, + bool? acceptConsentWalls, + ThemeMode? themeMode, + }) => + AppSettings( + aiProvider: aiProvider ?? this.aiProvider, + claudeApiKey: claudeApiKey ?? this.claudeApiKey, + claudeModel: claudeModel ?? this.claudeModel, + n8nWebhookUrl: n8nWebhookUrl ?? this.n8nWebhookUrl, + summaryLanguage: summaryLanguage ?? this.summaryLanguage, + maxAgeHours: maxAgeHours ?? this.maxAgeHours, + corsProxy: corsProxy ?? this.corsProxy, + hidePaid: hidePaid ?? this.hidePaid, + hideSponsored: hideSponsored ?? this.hideSponsored, + blockAds: blockAds ?? this.blockAds, + hideCookieBanners: hideCookieBanners ?? this.hideCookieBanners, + acceptConsentWalls: acceptConsentWalls ?? this.acceptConsentWalls, + themeMode: themeMode ?? this.themeMode, + ); +} diff --git a/lib/models/article.dart b/lib/models/article.dart new file mode 100644 index 0000000..08de71b --- /dev/null +++ b/lib/models/article.dart @@ -0,0 +1,30 @@ +import 'news_source.dart'; + +class Article { + const Article({ + required this.title, + required this.link, + required this.source, + this.published, + this.excerpt = '', + this.contentHtml = '', + this.imageUrl, + this.author, + }); + + final String title; + final String link; + final NewsSource source; + final DateTime? published; + + /// Short plain-text teaser from the feed (description / summary). + final String excerpt; + + /// Full article HTML if the feed ships it (content:encoded / atom content). + final String contentHtml; + final String? imageUrl; + final String? author; + + /// The link is unique enough to identify an article across refreshes. + String get id => link; +} diff --git a/lib/models/briefing.dart b/lib/models/briefing.dart new file mode 100644 index 0000000..220ac5f --- /dev/null +++ b/lib/models/briefing.dart @@ -0,0 +1,81 @@ +/// An AI generated digest of the day's news, grouped into topics. +class DailyBriefing { + const DailyBriefing({ + required this.createdAt, + required this.intro, + required this.topics, + required this.generatedBy, + }); + + final DateTime createdAt; + final String intro; + final List topics; + + /// Human readable name of the summarizer, e.g. "Claude (claude-opus-5-5)". + final String generatedBy; + + Map toJson() => { + 'createdAt': createdAt.toIso8601String(), + 'intro': intro, + 'generatedBy': generatedBy, + 'topics': topics.map((t) => t.toJson()).toList(), + }; + + factory DailyBriefing.fromJson(Map json) => DailyBriefing( + createdAt: DateTime.parse(json['createdAt'] as String), + intro: json['intro'] as String? ?? '', + generatedBy: json['generatedBy'] as String? ?? '', + topics: (json['topics'] as List? ?? []) + .map((t) => BriefingTopic.fromJson(t as Map)) + .toList(), + ); +} + +class BriefingTopic { + const BriefingTopic({ + required this.title, + required this.summary, + required this.sources, + }); + + final String title; + final String summary; + final List sources; + + Map toJson() => { + 'title': title, + 'summary': summary, + 'sources': sources.map((s) => s.toJson()).toList(), + }; + + factory BriefingTopic.fromJson(Map json) => BriefingTopic( + title: json['title'] as String? ?? '', + summary: json['summary'] as String? ?? '', + sources: (json['sources'] as List? ?? []) + .map((s) => ArticleRef.fromJson(s as Map)) + .toList(), + ); +} + +/// A lightweight pointer to an article, stored with the briefing so links +/// still work after the feed has been refreshed. +class ArticleRef { + const ArticleRef({ + required this.title, + required this.url, + required this.sourceName, + }); + + final String title; + final String url; + final String sourceName; + + Map toJson() => + {'title': title, 'url': url, 'sourceName': sourceName}; + + factory ArticleRef.fromJson(Map json) => ArticleRef( + title: json['title'] as String? ?? '', + url: json['url'] as String? ?? '', + sourceName: json['sourceName'] as String? ?? '', + ); +} diff --git a/lib/models/news_source.dart b/lib/models/news_source.dart new file mode 100644 index 0000000..63d47b5 --- /dev/null +++ b/lib/models/news_source.dart @@ -0,0 +1,55 @@ +enum SourceLanguage { + de('Deutsch', '🇩🇪'), + en('English', '🇬🇧'); + + const SourceLanguage(this.label, this.flag); + + final String label; + final String flag; +} + +class NewsSource { + const NewsSource({ + required this.id, + required this.name, + required this.feedUrl, + required this.homepage, + required this.language, + this.description = '', + this.enabledByDefault = false, + this.isCustom = false, + this.checkPaywallOnPage = false, + }); + + final String id; + final String name; + final String feedUrl; + final String homepage; + final SourceLanguage language; + final String description; + final bool enabledByDefault; + final bool isCustom; + + /// Paid articles are only marked inside the page (schema.org), not in the + /// feed, so each new article page is checked once. + final bool checkPaywallOnPage; + + Map toJson() => { + 'id': id, + 'name': name, + 'feedUrl': feedUrl, + 'homepage': homepage, + 'language': language.name, + 'description': description, + }; + + factory NewsSource.custom(Map json) => NewsSource( + id: json['id'] as String, + name: json['name'] as String, + feedUrl: json['feedUrl'] as String, + homepage: json['homepage'] as String? ?? '', + language: SourceLanguage.values.byName(json['language'] as String? ?? 'en'), + description: json['description'] as String? ?? '', + isCustom: true, + ); +} diff --git a/lib/services/article_extractor.dart b/lib/services/article_extractor.dart new file mode 100644 index 0000000..41ad6ac --- /dev/null +++ b/lib/services/article_extractor.dart @@ -0,0 +1,250 @@ +import 'package:flutter/foundation.dart'; +import 'package:html/dom.dart' as dom; +import 'package:html/parser.dart' as html_parser; +import 'package:http/http.dart' as http; + +import '../models/article.dart'; +import 'consent_service.dart'; +import 'content_blocking.dart'; +import 'feed_service.dart'; + +enum BlockType { heading, paragraph, listItem, quote, code, image, caption } + +/// One piece of an article in reader view. For images [text] is the URL. +class ReaderBlock { + const ReaderBlock(this.type, this.text); + + final BlockType type; + final String text; + + bool get isText => type != BlockType.image && type != BlockType.caption; +} + +class ArticleContent { + ArticleContent(this.blocks, {this.consentWall = false}) + : plainText = blocks.where((b) => b.isText).map((b) => b.text).join('\n\n'); + + static final empty = ArticleContent(const []); + + final List blocks; + + /// Text handed to the AI summarizer. + final String plainText; + + /// The site redirected to a consent page (accept tracking or subscribe) + /// instead of serving the article. + final bool consentWall; +} + +/// Turns an article page into reader-view blocks so it can be read inside the +/// app, and gives the AI more to work with than the feed teaser. +class ArticleExtractor { + ArticleExtractor({http.Client? client, ConsentService? consent}) + : _client = client ?? http.Client(), + _consent = consent; + + final http.Client _client; + final ConsentService? _consent; + + /// Hard cap on what we send to the model per article (~10k tokens). + static const maxChars = 40000; + + /// Loads the article page and returns whichever is longer: the extracted + /// page or the full content some feeds ship. Never throws; on failure + /// (paywall, bot protection, CORS on web) it falls back to feed content. + /// + /// With [acceptConsentWalls], a redirect to a consent page the + /// [ConsentService] knows is accepted once and the article fetched again. + Future load( + Article article, { + String corsProxy = '', + bool acceptConsentWalls = false, + }) async { + final base = Uri.parse(article.link); + final fromFeed = article.contentHtml.isEmpty + ? ArticleContent.empty + : parse(article.contentHtml, base, isFragment: true); + + if (kIsWeb && corsProxy.isEmpty) return fromFeed; + try { + final url = Uri.parse( + kIsWeb ? '$corsProxy${Uri.encodeComponent(article.link)}' : article.link); + var page = await _fetch(url); + final consent = _consent; + if (page.wallUrl != null && + acceptConsentWalls && + consent != null && + consent.canAccept(page.wallUrl!) && + await consent.accept(page.wallUrl!, wallHtml: FeedService.decodeBody(page.bytes))) { + page = await _fetch(url); + } + if (page.wallUrl != null) return ArticleContent(fromFeed.blocks, consentWall: true); + if (page.status != 200) return fromFeed; + final fromPage = parse(FeedService.decodeBody(page.bytes), base); + return fromPage.plainText.length > fromFeed.plainText.length ? fromPage : fromFeed; + } catch (_) { + return fromFeed; + } + } + + /// [wallUrl] is set when the request ended on a consent page. + Future<({int status, List bytes, Uri? wallUrl})> _fetch(Uri url) async { + final request = http.Request('GET', url); + if (!kIsWeb) { + request.headers['User-Agent'] = 'Mozilla/5.0 (DailyTechNews)'; + final cookie = _consent?.cookieHeader(url); + if (cookie != null) request.headers['Cookie'] = cookie; + } + // send() (unlike get()) keeps the final URL after redirects. + final response = await _client.send(request).timeout(const Duration(seconds: 20)); + final finalUrl = response is http.BaseResponseWithUrl + ? (response as http.BaseResponseWithUrl).url + : null; + final onWall = + finalUrl != null && ContentBlocking.consentWall.hasMatch(finalUrl.toString()); + if (response.statusCode != 200) { + await response.stream.listen(null).cancel(); + return (status: response.statusCode, bytes: const [], wallUrl: null); + } + final bytes = await response.stream.toBytes().timeout(const Duration(seconds: 20)); + return (status: 200, bytes: bytes, wallUrl: onWall ? finalUrl : null); + } + + static const _junkTags = + 'script, style, nav, header, footer, aside, form, noscript, button, svg, iframe, ' + 'template, dialog, ' + // Screen-reader-only text such as "(öffnet im neuen Fenster)". + '.sr-only, .visually-hidden, .visuallyhidden, .screen-reader-text, .go-vh'; + + /// Class/id fragments of page furniture that sits inside article bodies. + static final _junkMarker = RegExp( + r'comment|related|newsletter|share|social|promo|advert|paywall|subscribe|' + r'recommend|author-box|byline|breadcrumb|tags|toolbar|consent|cookie|card|native-ad', + caseSensitive: false, + ); + + static const _containerTags = {'div', 'section', 'ul', 'ol', 'span', 'aside'}; + + static ArticleContent parse(String html, Uri base, {bool isFragment = false}) { + final root = isFragment + ? (html_parser.parse('$html').body!) + : html_parser.parse(html).documentElement!; + root.querySelectorAll(_junkTags).forEach((dom.Element e) => e.remove()); + for (final e in root.querySelectorAll('*').toList()) { + if (!_containerTags.contains(e.localName)) continue; + final marker = '${e.className} ${e.id}'; + if (_junkMarker.hasMatch(marker)) e.remove(); + } + + // Many sites wrap every teaser in its own

, so pick whichever + // candidate container holds the most paragraph text. + dom.Element? best; + var bestScore = 0; + for (final candidate + in root.querySelectorAll('article, main, [itemprop="articleBody"]')) { + final score = _score(candidate); + if (score > bestScore) { + best = candidate; + bestScore = score; + } + } + // The body includes navigation and comments; only use it as a last resort. + best ??= isFragment ? root : root.querySelector('body'); + if (best == null) return ArticleContent.empty; + + final blocks = []; + final seenImages = {}; + var chars = 0; + void add(BlockType type, String text) { + if (chars > maxChars) return; + if (type == BlockType.image) { + if (!seenImages.add(text)) return; + } else { + chars += text.length; + } + blocks.add(ReaderBlock(type, text)); + } + + void walk(dom.Element el) { + switch (el.localName) { + case 'h1': + return; // The title is shown from the feed. + case 'h2' || 'h3' || 'h4': + final t = _clean(el.text); + if (t.isNotEmpty) add(BlockType.heading, t); + return; + case 'p': + final t = _clean(el.text); + if (t.length >= 25) add(BlockType.paragraph, t); + el.querySelectorAll('img').forEach((img) => _addImage(img, base, add)); + return; + case 'li': + // Nested lists: emit the inner items individually. + if (el.querySelector('ul, ol') != null) break; + final t = _clean(el.text); + // Items that are nothing but a link are tables of contents or nav. + final linkText = _clean(el.querySelectorAll('a').map((a) => a.text).join(' ')); + if (linkText == t) return; + if (t.length >= 20) add(BlockType.listItem, t); + return; + case 'blockquote': + final t = _clean(el.text); + if (t.isNotEmpty) add(BlockType.quote, t); + return; + case 'pre': + final t = el.text.trimRight(); + if (t.isNotEmpty) add(BlockType.code, t); + return; + case 'figcaption': + final t = _clean(el.text); + if (t.isNotEmpty) add(BlockType.caption, t); + return; + case 'img': + _addImage(el, base, add); + return; + } + for (final child in el.children) { + walk(child); + } + } + + walk(best); + return ArticleContent(blocks); + } + + static int _score(dom.Element root) => root + .querySelectorAll('p') + .map((e) => _clean(e.text).length) + .where((l) => l > 40) + .fold(0, (a, b) => a + b); + + static String _clean(String s) => s.replaceAll(RegExp(r'\s+'), ' ').trim(); + + static void _addImage( + dom.Element img, Uri base, void Function(BlockType, String) add) { + // Lazy-loading sites keep the real URL in data attributes. + final raw = img.attributes['data-src'] ?? + img.attributes['data-lazy-src'] ?? + img.attributes['src'] ?? + img.attributes['srcset']?.split(',').first.trim().split(' ').first; + if (raw == null || raw.isEmpty || raw.startsWith('data:') || raw.endsWith('.svg')) { + return; + } + final width = int.tryParse(img.attributes['width'] ?? ''); + if (width != null && width < 120) return; // icons, avatars, tracking pixels + // Images linking to another page are author avatars or related-story + // thumbnails; links to the full-size image file are fine. + for (var p = img.parent; p != null; p = p.parent) { + if (p.localName == 'a') { + final href = p.attributes['href'] ?? ''; + if (!RegExp(r'\.(jpe?g|png|webp|gif|avif)(\?|$)', caseSensitive: false) + .hasMatch(href)) { + return; + } + break; + } + } + final url = base.resolve(raw).toString(); + if (url.startsWith('http')) add(BlockType.image, url); + } +} diff --git a/lib/services/claude_summarizer.dart b/lib/services/claude_summarizer.dart new file mode 100644 index 0000000..4a919c4 --- /dev/null +++ b/lib/services/claude_summarizer.dart @@ -0,0 +1,175 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; + +import '../models/article.dart'; +import '../models/briefing.dart'; +import '../models/news_source.dart'; +import 'summarizer.dart'; + +/// Calls the Claude Messages API directly over HTTP (there is no official +/// Dart SDK). +class ClaudeSummarizer implements Summarizer { + ClaudeSummarizer({required this.apiKey, required this.model, http.Client? client}) + : _client = client ?? http.Client(); + + final String apiKey; + final String model; + final http.Client _client; + + static final _endpoint = Uri.parse('https://api.anthropic.com/v1/messages'); + + /// Models that support the server-side refusal fallback (`fallbacks: "default"`). + static const _fallbackModels = {'claude-opus-5-5', 'claude-sonnet-5-5'}; + + static const _retryStatus = {429, 500, 502, 503, 529}; + + @override + String get name => 'Claude ($model)'; + + @override + Future summarizeArticle( + Article article, String text, SourceLanguage language) async { + final system = 'You summarize tech news articles for a busy reader. ' + 'Write in ${languageName(language)}, regardless of the article language. ' + 'Start with a single-sentence TL;DR line prefixed with "TL;DR:", then 3-5 ' + 'short bullet points starting with "• " covering the key facts, numbers and ' + 'why it matters. Plain text only, no markdown headings or bold. If the text ' + 'is only a teaser, summarize what is there and say that the full article ' + 'was not available.'; + final user = 'Source: ${article.source.name}\n' + 'Title: ${article.title}\n' + 'URL: ${article.link}\n\n' + 'Article text:\n$text'; + return _send(system: system, user: user); + } + + @override + Future createBriefing( + List
articles, SourceLanguage language) async { + final input = BriefingInput(articles); + final system = 'You are the editor of a daily tech news briefing. You receive ' + "today's headlines from several German and English tech news sites, each " + 'with an id in square brackets. Pick the 6-10 most important stories for a ' + 'software developer interested in tech, merging coverage of the same story ' + 'from different sources into one topic. Prefer substantial news (releases, ' + 'security incidents, regulation, big business moves, AI) over deals, ' + 'reviews and opinion pieces. Write all text in ${languageName(language)}. ' + 'For each topic give a short title, a 2-4 sentence summary, and the ids of ' + 'the articles it is based on. The intro is one or two sentences about the ' + "day's overall picture."; + final user = "Today's headlines:\n\n${input.toPromptList()}"; + + final raw = await _send(system: system, user: user, schema: _briefingSchema); + try { + return input.toBriefing(jsonDecode(raw) as Map, name); + } on FormatException { + throw SummarizerException('Claude returned an unexpected briefing format.'); + } + } + + static const _briefingSchema = { + 'type': 'object', + 'properties': { + 'intro': {'type': 'string'}, + 'topics': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'title': {'type': 'string'}, + 'summary': {'type': 'string'}, + 'article_ids': { + 'type': 'array', + 'items': {'type': 'string'}, + }, + }, + 'required': ['title', 'summary', 'article_ids'], + 'additionalProperties': false, + }, + }, + }, + 'required': ['intro', 'topics'], + 'additionalProperties': false, + }; + + Future _send({ + required String system, + required String user, + Map? schema, + }) async { + final useFallbacks = _fallbackModels.contains(model); + final body = jsonEncode({ + 'model': model, + 'max_tokens': 16000, + 'system': system, + 'messages': [ + {'role': 'user', 'content': user}, + ], + // Summarizing is a routine task; low effort keeps it fast and cheap. + 'output_config': { + 'effort': 'low', + if (schema != null) 'format': {'type': 'json_schema', 'schema': schema}, + }, + // If a safety classifier declines, retry server-side on a fallback model. + if (useFallbacks) 'fallbacks': 'default', + }); + final headers = { + 'content-type': 'application/json', + 'x-api-key': apiKey, + 'anthropic-version': '2023-06-01', + if (useFallbacks) 'anthropic-beta': 'server-side-fallback-2026-07-01', + if (kIsWeb) 'anthropic-dangerous-direct-browser-access': 'true', + }; + + http.Response response; + var attempt = 0; + while (true) { + try { + response = await _client + .post(_endpoint, headers: headers, body: body) + .timeout(const Duration(minutes: 3)); + } on Exception catch (e) { + if (attempt++ < 2) { + await Future.delayed(Duration(seconds: 2 * attempt)); + continue; + } + throw SummarizerException('Could not reach the Claude API: $e'); + } + if (_retryStatus.contains(response.statusCode) && attempt++ < 2) { + final retryAfter = int.tryParse(response.headers['retry-after'] ?? ''); + await Future.delayed(Duration(seconds: retryAfter ?? 3 * attempt)); + continue; + } + break; + } + + final json = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + if (response.statusCode != 200) { + final error = json['error'] as Map?; + final message = error?['message'] ?? 'Unknown error'; + throw SummarizerException(switch (response.statusCode) { + 401 => 'Invalid Claude API key. Check it in Settings.', + 429 => 'Rate limited by the Claude API. Try again in a minute.', + 529 => 'The Claude API is overloaded right now. Try again shortly.', + _ => 'Claude API error ${response.statusCode}: $message', + }); + } + + switch (json['stop_reason']) { + case 'refusal': + throw SummarizerException('Claude declined to summarize this content.'); + case 'max_tokens' when schema != null: + throw SummarizerException('The briefing was cut off. Try fewer sources.'); + } + + final text = (json['content'] as List) + .where((block) => block['type'] == 'text') + .map((block) => block['text'] as String) + .join() + .trim(); + if (text.isEmpty) throw SummarizerException('Claude returned an empty response.'); + return text; + } +} diff --git a/lib/services/consent_service.dart b/lib/services/consent_service.dart new file mode 100644 index 0000000..94ae6f4 --- /dev/null +++ b/lib/services/consent_service.dart @@ -0,0 +1,104 @@ +import 'package:http/http.dart' as http; + +/// Gets past consent walls: sites that redirect to a separate consent page +/// instead of showing the article (currently Golem). +/// +/// Accepting makes the same request the site's own "accept" button makes and +/// keeps the resulting cookies in memory, so both the reader view (via +/// [cookieHeader]) and the in-app web view (via [cookiesFor]) can send them. +/// Ads and trackers stay blocked separately. +class ConsentService { + ConsentService({http.Client? client}) : _client = client ?? http.Client(); + + final http.Client _client; + + /// Domain (e.g. "golem.de") -> cookie name -> value. + final Map> _cookies = {}; + + static final _walls = <_ConsentWall>[_GolemWall()]; + + static String domainOf(Uri url) { + final parts = url.host.split('.'); + return parts.length <= 2 ? url.host : parts.sublist(parts.length - 2).join('.'); + } + + /// True if [url] is a consent page this service knows how to accept. + bool canAccept(Uri url) => _walls.any((w) => w.matches(url)); + + Map cookiesFor(Uri url) => _cookies[domainOf(url)] ?? const {}; + + /// Value for a `Cookie` request header, or null if nothing was accepted. + String? cookieHeader(Uri url) { + final cookies = cookiesFor(url); + if (cookies.isEmpty) return null; + return cookies.entries.map((e) => '${e.key}=${e.value}').join('; '); + } + + /// Accepts the consent wall at [wallUrl]. [wallHtml] is the consent page, + /// fetched if not given. Returns true if consent cookies were obtained. + Future accept(Uri wallUrl, {String? wallHtml}) async { + final wall = _walls.where((w) => w.matches(wallUrl)).firstOrNull; + if (wall == null) return false; + try { + wallHtml ??= (await _client.get(wallUrl, headers: _headers) + .timeout(const Duration(seconds: 15))) + .body; + final response = await _client + .get(wall.acceptUrl(wallUrl, wallHtml), headers: _headers) + .timeout(const Duration(seconds: 15)); + if (response.statusCode != 200) return false; + final cookies = parseSetCookie(response.headers['set-cookie'] ?? ''); + if (!cookies.containsKey(wall.requiredCookie)) return false; + _cookies.putIfAbsent(domainOf(wallUrl), () => {}).addAll(cookies); + return true; + } catch (_) { + return false; + } + } + + static const _headers = {'User-Agent': 'Mozilla/5.0 (DailyTechNews)'}; + + /// package:http joins multiple Set-Cookie headers with commas. Split only on + /// commas that start a new `name=` pair, not those inside `expires=` dates. + static Map parseSetCookie(String header) { + final cookies = {}; + for (final cookie in header.split(RegExp(r',(?=\s*[^;,=\s]+=)'))) { + final pair = cookie.split(';').first.trim(); + final eq = pair.indexOf('='); + if (eq > 0) cookies[pair.substring(0, eq)] = pair.substring(eq + 1); + } + return cookies; + } +} + +abstract class _ConsentWall { + bool matches(Uri url); + + /// The cookie that proves consent was stored. + String get requiredCookie; + + Uri acceptUrl(Uri wallUrl, String wallHtml); +} + +/// Golem redirects to /sonstiges/zustimmung/auswahl.html. Its consent script +/// stores the choice by calling /abo/setconsentcookie.php; "simple" is the +/// plain "Zustimmen" button Golem shows when its consent manager doesn't load. +class _GolemWall implements _ConsentWall { + static final _version = RegExp(r'"cookieVersion"\s*:\s*"([^"]+)"'); + + @override + bool matches(Uri url) => + url.host.endsWith('golem.de') && url.path.startsWith('/sonstiges/zustimmung/'); + + @override + String get requiredCookie => 'golem_consent20'; + + @override + Uri acceptUrl(Uri wallUrl, String wallHtml) => Uri.https('www.golem.de', '/abo/setconsentcookie.php', { + 'type': 'simple', + // Golem bumps this when its consent text changes; read it from the page. + 'version': _version.firstMatch(wallHtml)?.group(1) ?? '|250101', + 'expire': '1', + 'referer': wallUrl.queryParameters['from'] ?? 'https://www.golem.de/', + }); +} diff --git a/lib/services/content_blocking.dart b/lib/services/content_blocking.dart new file mode 100644 index 0000000..3f1954e --- /dev/null +++ b/lib/services/content_blocking.dart @@ -0,0 +1,136 @@ +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + +/// Ad, tracker and cookie-banner blocking for the in-app "original page" view. +/// +/// Two layers: requests to known ad/tracker/consent hosts are blocked before +/// they load, and a stylesheet injected at page start hides whatever slots and +/// banners are left. Hiding a consent banner gives no consent, so sites treat +/// it like a rejection. +class ContentBlocking { + /// Sites that redirect to a separate consent page instead of showing a + /// banner. Nothing can be hidden there; it's accept, pay, or leave. + static final consentWall = RegExp(r'golem\.de/sonstiges/zustimmung/|consent\.yahoo\.com/'); + + static const adHosts = [ + // Ad exchanges and networks + 'doubleclick.net', 'googlesyndication.com', 'googleadservices.com', + 'googletagservices.com', 'adservice.google.com', 'adservice.google.de', + 'amazon-adsystem.com', 'criteo.com', 'criteo.net', 'taboola.com', 'outbrain.com', + 'adnxs.com', 'rubiconproject.com', 'pubmatic.com', 'openx.net', 'casalemedia.com', + 'indexww.com', 'smartadserver.com', 'sascdn.com', 'yieldlab.net', 'adform.net', + 'adition.com', 'teads.tv', 'seedtag.com', '3lift.com', 'sharethrough.com', + '33across.com', 'media.net', 'mgid.com', 'zemanta.com', 'yieldmo.com', + 'onetag-sys.com', 'bidswitch.net', 'lijit.com', 'sonobi.com', 'gumgum.com', + 'adsrvr.org', 'plista.com', 'ligatus.com', 'nativendo.de', 'traffective.com', + 'btloader.com', 'ad-delivery.net', 'adsafeprotected.com', 'moatads.com', + 'confiant-integrations.net', 'flashtalking.com', 'servedby-buysellads.com', + // Tracking and analytics + 'google-analytics.com', 'googletagmanager.com', 'scorecardresearch.com', + 'chartbeat.com', 'chartbeat.net', 'ioam.de', 'iocnt.net', 'hotjar.com', + 'facebook.net', 'ads-twitter.com', 'analytics.tiktok.com', 'bat.bing.com', + 'quantserve.com', 'krxd.net', 'demdex.net', 'omtrdc.net', 'adobedtm.com', + 'permutive.com', 'permutive.app', 'cxense.com', 'id5-sync.com', 'rlcdn.com', + 'bluekai.com', 'everesttech.net', 'parsely.com', 'newrelic.com', 'nr-data.net', + ]; + + static const consentHosts = [ + 'privacy-mgmt.com', 'sp-prod.net', 'sourcepoint.com', // Sourcepoint + 'cookielaw.org', 'onetrust.com', 'onetrust.io', // OneTrust + 'usercentrics.eu', 'usercentrics.com', 'privacy-center.org', 'didomi.io', + 'consensu.org', 'cookiebot.com', 'consentmanager.net', 'trustarc.com', + 'truste.com', 'cookieinformation.com', 'iubenda.com', 'cookie-script.com', + 'termly.io', 'osano.com', 'contentpass.net', 'contentpass.de', 'fundingchoicesmessages.google.com', + ]; + + static const _adSelectors = [ + 'ins.adsbygoogle', '.adsbygoogle', '[id^="google_ads_iframe"]', '[id^="div-gpt-ad"]', + '[data-google-query-id]', 'iframe[src*="doubleclick"]', '[id*="taboola"]', + '[class*="taboola"]', '[id*="outbrain"]', '.OUTBRAIN', '[data-ad-slot]', + '[data-ad-unit]', '.ad-container', '.ad-slot', '.ad-wrapper', '.ad-label', + '.advertisement', '.advert', '.adzone', '.adunit', '.sponsored-content', + '[aria-label="Advertisement"]', '[data-testid*="ad-slot"]', + // Reserved slots that would otherwise stay as empty gaps (e.g. ComputerBase). + '[class*="adbox"]', '[class*="adslot"]', '[class*="ad-slot"]', '[class*="ad_slot"]', + '[id*="adslot"]', r'[class$="-ad"]', '[class*="-ad "]', '[class*="ad-container"]', + ]; + + static const _consentSelectors = [ + '[id^="sp_message_container"]', '.sp_veil', '#onetrust-consent-sdk', + '#onetrust-banner-sdk', '.onetrust-pc-dark-filter', '#usercentrics-root', + '#usercentrics-cmp-ui', '#didomi-host', '#CybotCookiebotDialog', + '#CybotCookiebotDialogBodyUnderlay', '#cmpbox', '#cmpbox2', '.cmpboxBG', + '#qc-cmp2-container', '.qc-cmp2-container', '#truste-consent-track', + '.truste_overlay', '.truste_box_overlay', '#iubenda-cs-banner', '.fc-consent-root', + '.fc-dialog-overlay', '#cookie-banner', '#cookiebanner', '#cookie-consent', + '.cookie-banner', '.cookie-consent', '.cc-window', '.cc-banner', + ]; + + static String _hostPattern(List hosts) => + r'^https?://([^/:]+\.)?(' + hosts.map(RegExp.escape).join('|') + r')([:/?]|$)'; + + /// Network-level blocking. One combined regex per category keeps the + /// per-request check cheap. + static List blockers({required bool ads, required bool cookies}) => [ + if (ads) _block(_hostPattern(adHosts)), + if (cookies) ...[ + _block(_hostPattern(consentHosts)), + // Sourcepoint is often served from the site itself, e.g. cmp.heise.de. + _block(r'^https?://cmp\.[^/]+/'), + ], + ]; + + static ContentBlocker _block(String pattern) => ContentBlocker( + trigger: ContentBlockerTrigger(urlFilter: pattern), + action: ContentBlockerAction(type: ContentBlockerActionType.BLOCK), + ); + + /// A script for page start that injects the hiding stylesheet and keeps it + /// in place. Returns null when nothing is enabled. + static String? cosmeticScript({required bool ads, required bool cookies}) { + final selectors = [if (ads) ..._adSelectors, if (cookies) ..._consentSelectors]; + if (selectors.isEmpty) return null; + final css = StringBuffer('${selectors.join(',')}{display:none!important}'); + if (cookies) { + // Consent dialogs lock scrolling on /; undo that. + css.write('html,body{overflow:auto!important;overflow-y:auto!important}'); + } + final cssLiteral = css.toString().replaceAll(r'\', r'\\').replaceAll("'", r"\'"); + return ''' +(function () { + var css = '$cssLiteral'; + var unlockConsent = $cookies; + var consentPattern = /consent|cookie|cmp|gdpr|sp-message|didomi|onetrust|usercentrics/i; + function apply() { + if (document.getElementById('tagesbyte-blocker')) return; + var style = document.createElement('style'); + style.id = 'tagesbyte-blocker'; + style.textContent = css; + (document.head || document.documentElement).appendChild(style); + } + // Some sites (e.g. ComputerBase) build their own consent wall as a modal + // , which makes the rest of the page inert even when hidden. + function unlock() { + if (!unlockConsent) return; + document.querySelectorAll('dialog').forEach(function (d) { + if (consentPattern.test(d.className + ' ' + d.id)) { + try { d.close(); } catch (e) {} + d.remove(); + } + }); + [document.documentElement, document.body].forEach(function (el) { + if (!el) return; + Array.prototype.slice.call(el.classList).forEach(function (c) { + if (consentPattern.test(c)) el.classList.remove(c); + }); + }); + } + apply(); + document.addEventListener('DOMContentLoaded', function () { apply(); unlock(); }); + window.addEventListener('load', function () { apply(); unlock(); }); + // Consent scripts often open their dialog a moment after load. + var runs = 0; + var timer = setInterval(function () { unlock(); if (++runs > 30) clearInterval(timer); }, 500); +})(); +'''; + } +} diff --git a/lib/services/feed_parser.dart b/lib/services/feed_parser.dart new file mode 100644 index 0000000..00091c5 --- /dev/null +++ b/lib/services/feed_parser.dart @@ -0,0 +1,198 @@ +import 'package:html/dom.dart' as dom; +import 'package:html/parser.dart' as html_parser; +import 'package:xml/xml.dart'; + +import '../models/article.dart'; +import '../models/news_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) { + final doc = XmlDocument.parse(body); + final root = doc.rootElement; + if (root.name.local == 'feed') { + return root.childElements + .where((e) => e.name.local == 'entry') + .map((e) => _parseAtomEntry(e, source)) + .whereType
() + .toList(); + } + // RSS 2.0 nests in , RSS 1.0 puts them at the root. + return root.descendantElements + .where((e) => e.name.local == 'item') + .map((e) => _parseRssItem(e, source)) + .whereType
() + .toList(); + } + + static Article? _parseRssItem(XmlElement item, NewsSource source) { + final title = htmlToText(_text(item, 'title')); + final link = _text(item, 'link').trim().isNotEmpty + ? _text(item, 'link').trim() + : _text(item, 'guid').trim(); + if (title.isEmpty || !link.startsWith('http')) return null; + + final descriptionHtml = _text(item, 'description'); + final contentHtml = _text(item, 'encoded', prefix: 'content'); + final date = _text(item, 'pubDate').isNotEmpty + ? _text(item, 'pubDate') + : _text(item, 'date', prefix: 'dc'); + + return Article( + title: title, + link: link, + source: source, + published: parseDate(date), + excerpt: _excerpt(descriptionHtml.isNotEmpty ? descriptionHtml : contentHtml), + contentHtml: contentHtml, + imageUrl: _imageFrom(item) ?? _firstImg(contentHtml) ?? _firstImg(descriptionHtml), + author: _nonEmpty(htmlToText(_text(item, 'creator', prefix: 'dc'))) ?? + _nonEmpty(_text(item, 'author')), + ); + } + + static Article? _parseAtomEntry(XmlElement entry, NewsSource source) { + final title = htmlToText(_text(entry, 'title')); + final links = entry.childElements.where((e) => e.name.local == 'link'); + final link = links + .where((l) => (l.getAttribute('rel') ?? 'alternate') == 'alternate') + .map((l) => l.getAttribute('href')) + .firstWhere((h) => h != null, orElse: () => null) ?? + links.map((l) => l.getAttribute('href')).firstWhere((h) => h != null, orElse: () => null); + if (title.isEmpty || link == null || !link.startsWith('http')) return null; + + final summaryHtml = _text(entry, 'summary'); + final contentHtml = _text(entry, 'content'); + final date = _text(entry, 'published').isNotEmpty + ? _text(entry, 'published') + : _text(entry, 'updated'); + final authorEl = _child(entry, 'author'); + + return Article( + title: title, + link: link, + source: source, + published: parseDate(date), + excerpt: _excerpt(summaryHtml.isNotEmpty ? summaryHtml : contentHtml), + contentHtml: contentHtml, + imageUrl: _imageFrom(entry) ?? _firstImg(contentHtml) ?? _firstImg(summaryHtml), + author: authorEl == null ? null : _nonEmpty(_text(authorEl, 'name')), + ); + } + + static XmlElement? _child(XmlElement parent, String local, {String? prefix}) { + for (final e in parent.childElements) { + if (e.name.local == local && (prefix == null || e.name.prefix == prefix)) { + return e; + } + } + return null; + } + + static String _text(XmlElement parent, String local, {String? prefix}) => + _child(parent, local, prefix: prefix)?.innerText.trim() ?? ''; + + static String? _nonEmpty(String s) => s.trim().isEmpty ? null : s.trim(); + + static String? _imageFrom(XmlElement item) { + for (final e in item.descendantElements) { + final local = e.name.local; + final url = e.getAttribute('url'); + if (url == null) continue; + if (local == 'thumbnail') return url; + if (local == 'content' && e.name.prefix == 'media') { + final medium = e.getAttribute('medium') ?? e.getAttribute('type') ?? 'image'; + if (medium.startsWith('image')) return url; + } + if (local == 'enclosure' && (e.getAttribute('type') ?? '').startsWith('image')) { + return url; + } + } + return null; + } + + static String? _firstImg(String html) { + if (!html.contains(' 0 ? lastSpace : excerptLength)}…'; + } + + /// Converts an HTML snippet to whitespace-normalized plain text. + static String htmlToText(String html) { + if (html.isEmpty) return ''; + final fragment = html_parser.parseFragment(html); + fragment.querySelectorAll('script, style').forEach((dom.Element e) => e.remove()); + return (fragment.text ?? '').replaceAll(RegExp(r'\s+'), ' ').trim(); + } + + static const _months = { + 'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, + 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12, + }; + + static const _zones = { + 'UT': 0, 'GMT': 0, 'Z': 0, 'UTC': 0, + 'EST': -5, 'EDT': -4, 'CST': -6, 'CDT': -5, + 'MST': -7, 'MDT': -6, 'PST': -8, 'PDT': -7, + 'CET': 1, 'CEST': 2, + }; + + static final _rfc822 = RegExp( + r'^(?:\w{3},\s*)?(\d{1,2})\s+(\w{3})\w*\s+(\d{2,4})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*([+-]\d{4}|[A-Za-z]+)?', + ); + + /// Parses ISO 8601 (Atom) and RFC 822 (RSS) dates. Returns UTC. + static DateTime? parseDate(String raw) { + final s = raw.trim(); + if (s.isEmpty) return null; + final iso = DateTime.tryParse(s); + if (iso != null) return iso.toUtc(); + + final m = _rfc822.firstMatch(s); + if (m == null) return null; + final month = _months[m.group(2)!.toLowerCase()]; + if (month == null) return null; + var year = int.parse(m.group(3)!); + if (year < 100) year += 2000; + final utc = DateTime.utc( + year, + month, + int.parse(m.group(1)!), + int.parse(m.group(4)!), + int.parse(m.group(5)!), + int.parse(m.group(6) ?? '0'), + ); + + final zone = m.group(7); + var offset = Duration.zero; + if (zone != null && (zone.startsWith('+') || zone.startsWith('-'))) { + final sign = zone.startsWith('-') ? -1 : 1; + offset = Duration( + hours: int.parse(zone.substring(1, 3)), + minutes: int.parse(zone.substring(3, 5)), + ) * sign; + } else if (zone != null) { + offset = Duration(hours: _zones[zone.toUpperCase()] ?? 0); + } + return utc.subtract(offset); + } +} diff --git a/lib/services/feed_service.dart b/lib/services/feed_service.dart new file mode 100644 index 0000000..7d4c631 --- /dev/null +++ b/lib/services/feed_service.dart @@ -0,0 +1,98 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; + +import '../models/article.dart'; +import '../models/news_source.dart'; +import 'feed_parser.dart'; + +class FeedResult { + const FeedResult(this.articles, this.errors); + + final List
articles; + + /// Source name -> error message for feeds that failed to load. + final Map errors; +} + +class FeedService { + FeedService({http.Client? client}) : _client = client ?? http.Client(); + + final http.Client _client; + + static const _timeout = Duration(seconds: 20); + + /// Fetches all [sources] in parallel. Failing feeds are reported in + /// [FeedResult.errors] instead of failing the whole refresh. + /// + /// [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 maxAge, + String corsProxy = '', + }) async { + final errors = {}; + final results = await Future.wait(sources.map((source) async { + try { + return await fetchSource(source, corsProxy: corsProxy); + } catch (e) { + errors[source.name] = _describe(e); + return
[]; + } + })); + + final cutoff = DateTime.now().toUtc().subtract(maxAge); + final seen = {}; + final articles =
[ + for (final list in results) + for (final a in list) + if ((a.published == null || a.published!.isAfter(cutoff)) && seen.add(a.id)) a, + ]; + articles.sort((a, b) { + final pa = a.published, pb = b.published; + if (pa == null && pb == null) return 0; + if (pa == null) return 1; + if (pb == null) return -1; + return pb.compareTo(pa); + }); + return FeedResult(articles, errors); + } + + Future> fetchSource(NewsSource source, {String corsProxy = ''}) async { + final url = kIsWeb && corsProxy.isNotEmpty + ? '$corsProxy${Uri.encodeComponent(source.feedUrl)}' + : source.feedUrl; + final response = await _client.get( + Uri.parse(url), + headers: { + 'Accept': 'application/rss+xml, application/atom+xml, application/xml, text/xml, */*', + if (!kIsWeb) 'User-Agent': 'DailyTechNews/0.1 (Flutter RSS reader)', + }, + ).timeout(_timeout); + if (response.statusCode != 200) { + throw Exception('HTTP ${response.statusCode}'); + } + return FeedParser.parse(decodeBody(response.bodyBytes), source); + } + + /// Decodes feed bytes honoring the XML declaration's encoding; defaults to UTF-8. + static String decodeBody(List bytes) { + final head = latin1.decode(bytes.take(200).toList()).toLowerCase(); + final match = RegExp(r'''encoding=["']([\w-]+)["']''').firstMatch(head); + final encoding = match?.group(1); + if (encoding == 'iso-8859-1' || encoding == 'latin1' || encoding == 'windows-1252') { + return latin1.decode(bytes); + } + return utf8.decode(bytes, allowMalformed: true); + } + + static String _describe(Object e) { + final text = e.toString(); + if (kIsWeb && text.contains('ClientException')) { + return 'Blocked by the browser (CORS). Set a CORS proxy in Settings.'; + } + return text.replaceFirst('Exception: ', ''); + } +} diff --git a/lib/services/n8n_summarizer.dart b/lib/services/n8n_summarizer.dart new file mode 100644 index 0000000..983629b --- /dev/null +++ b/lib/services/n8n_summarizer.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import '../models/article.dart'; +import '../models/briefing.dart'; +import '../models/news_source.dart'; +import 'summarizer.dart'; + +/// Delegates summarizing to an existing n8n workflow via a Webhook trigger. +/// The request/response contract is documented in README.md. +class N8nSummarizer implements Summarizer { + N8nSummarizer({required this.webhookUrl, http.Client? client}) + : _client = client ?? http.Client(); + + final String webhookUrl; + final http.Client _client; + + @override + String get name => 'n8n workflow'; + + @override + Future summarizeArticle( + Article article, String text, SourceLanguage language) async { + final body = await _post({ + 'action': 'summarize_article', + 'language': language.name, + 'article': { + 'title': article.title, + 'url': article.link, + 'source': article.source.name, + 'published': article.published?.toIso8601String(), + 'text': text, + }, + }); + if (body is Map && body['summary'] is String) return body['summary'] as String; + if (body is String && body.trim().isNotEmpty) return body.trim(); + throw SummarizerException('n8n response has no "summary" field.'); + } + + @override + Future createBriefing( + List
articles, SourceLanguage language) async { + final input = BriefingInput(articles); + final body = await _post({ + 'action': 'daily_briefing', + 'language': language.name, + 'articles': input.toJsonList(), + }); + if (body is Map && body['topics'] is List) { + return input.toBriefing(body, name); + } + // Accept a plain text digest (e.g. the message the workflow posts to Discord). + final text = body is Map ? body['text'] ?? body['summary'] : body; + if (text is String && text.trim().isNotEmpty) { + return DailyBriefing( + createdAt: DateTime.now(), + intro: text.trim(), + topics: const [], + generatedBy: name, + ); + } + throw SummarizerException('n8n response has neither "topics" nor "text".'); + } + + Future _post(Map payload) async { + final http.Response response; + try { + response = await _client + .post( + Uri.parse(webhookUrl), + headers: {'content-type': 'application/json'}, + body: jsonEncode(payload), + ) + .timeout(const Duration(minutes: 3)); + } on Exception catch (e) { + throw SummarizerException('Could not reach the n8n webhook: $e'); + } + if (response.statusCode != 200) { + throw SummarizerException('n8n webhook returned HTTP ${response.statusCode}.'); + } + final text = utf8.decode(response.bodyBytes); + try { + final json = jsonDecode(text); + // "Respond to Webhook" with "All Incoming Items" wraps results in a list. + return json is List && json.isNotEmpty ? json.first : json; + } on FormatException { + return text; + } + } +} diff --git a/lib/services/paywall_service.dart b/lib/services/paywall_service.dart new file mode 100644 index 0000000..c4d9e7d --- /dev/null +++ b/lib/services/paywall_service.dart @@ -0,0 +1,105 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import '../models/article.dart'; +import 'storage.dart'; + +/// Recognizes paid and sponsored articles so the feed can hide them. +/// +/// Most paid articles are marked in the title (heise+, Golem's "(g+)"). +/// SPIEGEL and t3n only mark them in the page's schema.org data +/// (`"isAccessibleForFree": false`), so for those sources each new article is +/// checked once in the background and the result is remembered. +class PaywallService { + PaywallService(this._storage, {http.Client? client}) + : _client = client ?? http.Client(), + _checks = _storage.loadPaywallChecks(); + + final Storage _storage; + final http.Client _client; + + /// Article URL -> (paid, checked at). + final Map _checks; + + static final paidTitle = RegExp( + r'^\s*(heise\+|\(g\+\)|SPIEGEL\+|Z\+|t3n Pro\b|c.t\+)', + caseSensitive: false, + ); + + static final sponsoredTitle = RegExp( + r'^\s*(Anzeige|Sponsored|Advertorial|Werbung|Promotion)\s*[:|]', + caseSensitive: false, + ); + + static final _marker = + RegExp(r'''"isAccessibleForFree"\s*:\s*"?(true|false)"?''', caseSensitive: false); + + /// Stop reading a page after this much without finding the marker. + static const _maxBytes = 1500000; + static const _concurrency = 6; + static const _keepFor = Duration(days: 14); + + bool isPaid(Article a) => paidTitle.hasMatch(a.title) || (_checks[a.link]?.paid ?? false); + + bool isSponsored(Article a) => sponsoredTitle.hasMatch(a.title); + + /// Checks articles from page-checked sources that haven't been seen yet. + /// Returns true if any new paid article was found. + Future checkPages(Iterable
articles) async { + final queue = articles + .where((a) => + a.source.checkPaywallOnPage && + !_checks.containsKey(a.link) && + !paidTitle.hasMatch(a.title)) + .toList(); + if (queue.isEmpty) return false; + + var foundPaid = false; + Future worker() async { + while (queue.isNotEmpty) { + final article = queue.removeLast(); + final paid = await _fetchIsPaid(article.link); + if (paid == null) continue; // network error: try again next refresh + _checks[article.link] = (paid: paid, checkedAt: DateTime.now()); + foundPaid |= paid; + } + } + + await Future.wait(List.generate(_concurrency, (_) => worker())); + final cutoff = DateTime.now().subtract(_keepFor); + _checks.removeWhere((_, v) => v.checkedAt.isBefore(cutoff)); + await _storage.savePaywallChecks(_checks); + return foundPaid; + } + + /// Streams the page and stops as soon as the schema.org marker shows up. + /// Returns null on network errors, false if the page has no marker. + Future _fetchIsPaid(String url) async { + try { + final request = http.Request('GET', Uri.parse(url)) + ..headers['User-Agent'] = 'Mozilla/5.0 (DailyTechNews)'; + final response = await _client.send(request).timeout(const Duration(seconds: 15)); + if (response.statusCode != 200) { + await response.stream.listen(null).cancel(); + return null; + } + var tail = ''; + var read = 0; + await for (final chunk in response.stream.timeout(const Duration(seconds: 15))) { + read += chunk.length; + // The marker is ASCII, so latin1 avoids errors on split UTF-8 sequences. + // Keep a little overlap in case the marker spans two chunks. + final text = tail + latin1.decode(chunk); + final match = _marker.firstMatch(text); + if (match != null) return match.group(1)!.toLowerCase() == 'false'; + if (read > _maxBytes) break; + tail = text.length > 64 ? text.substring(text.length - 64) : text; + } + return false; + } catch (_) { + return null; + } + } +} diff --git a/lib/services/storage.dart b/lib/services/storage.dart new file mode 100644 index 0000000..dab305a --- /dev/null +++ b/lib/services/storage.dart @@ -0,0 +1,165 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart' show ThemeMode; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../models/app_settings.dart'; +import '../models/briefing.dart'; +import '../models/news_source.dart'; + +/// Persists settings, source selection and the last briefing. +/// The Claude API key goes to the platform keystore, everything else to +/// shared preferences. +class Storage { + Storage(this._prefs); + + static Future open() async => Storage(await SharedPreferences.getInstance()); + + final SharedPreferences _prefs; + final _secure = const FlutterSecureStorage(); + + static const _apiKey = 'claude_api_key'; + + Future loadSettings() async { + String apiKey = ''; + try { + apiKey = await _secure.read(key: _apiKey) ?? ''; + } catch (_) { + // Keystore unavailable (e.g. some web/desktop setups) -> user re-enters it. + } + return AppSettings( + aiProvider: AiProvider.values.byName(_prefs.getString('ai_provider') ?? 'none'), + claudeApiKey: apiKey, + claudeModel: _prefs.getString('claude_model') ?? 'claude-opus-5-5', + n8nWebhookUrl: _prefs.getString('n8n_webhook_url') ?? '', + summaryLanguage: + SourceLanguage.values.byName(_prefs.getString('summary_language') ?? 'de'), + maxAgeHours: _prefs.getInt('max_age_hours') ?? 24, + corsProxy: _prefs.getString('cors_proxy') ?? '', + hidePaid: _prefs.getBool('hide_paid') ?? true, + hideSponsored: _prefs.getBool('hide_sponsored') ?? true, + blockAds: _prefs.getBool('block_ads') ?? true, + hideCookieBanners: _prefs.getBool('hide_cookie_banners') ?? true, + acceptConsentWalls: _prefs.getBool('accept_consent_walls') ?? true, + themeMode: ThemeMode.values.asNameMap()[_prefs.getString('theme_mode')] ?? ThemeMode.system, + ); + } + + Future saveSettings(AppSettings s) async { + await _prefs.setString('ai_provider', s.aiProvider.name); + await _prefs.setString('claude_model', s.claudeModel); + await _prefs.setString('n8n_webhook_url', s.n8nWebhookUrl); + await _prefs.setString('summary_language', s.summaryLanguage.name); + await _prefs.setInt('max_age_hours', s.maxAgeHours); + await _prefs.setString('cors_proxy', s.corsProxy); + await _prefs.setBool('hide_paid', s.hidePaid); + await _prefs.setBool('hide_sponsored', s.hideSponsored); + await _prefs.setBool('block_ads', s.blockAds); + await _prefs.setBool('hide_cookie_banners', s.hideCookieBanners); + await _prefs.setBool('accept_consent_walls', s.acceptConsentWalls); + await _prefs.setString('theme_mode', s.themeMode.name); + try { + await _secure.write(key: _apiKey, value: s.claudeApiKey); + } catch (_) {} + } + + /// Null means "never configured" -> use each source's default. + Set? loadEnabledSourceIds() => + _prefs.getStringList('enabled_sources')?.toSet(); + + Future saveEnabledSourceIds(Set ids) => + _prefs.setStringList('enabled_sources', ids.toList()); + + List loadCustomSources() { + final raw = _prefs.getString('custom_sources'); + if (raw == null) return []; + return (jsonDecode(raw) as List) + .map((j) => NewsSource.custom(j as Map)) + .toList(); + } + + 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. + Map loadPaywallChecks() { + final raw = _prefs.getString('paywall_checks'); + if (raw == null) return {}; + try { + return (jsonDecode(raw) as Map).map((url, v) { + final entry = v as List; + return MapEntry(url, ( + paid: entry[0] as bool, + checkedAt: DateTime.fromMillisecondsSinceEpoch(entry[1] as int), + )); + }); + } catch (_) { + return {}; + } + } + + Future savePaywallChecks(Map checks) => + _prefs.setString( + 'paywall_checks', + jsonEncode(checks.map( + (url, v) => MapEntry(url, [v.paid, v.checkedAt.millisecondsSinceEpoch]))), + ); + + /// All saved briefings, newest first. Stored as a JSON file in the app's + /// private storage (preferences on web), since the history keeps growing. + Future> loadBriefingHistory() async { + List history; + try { + String? raw; + if (kIsWeb) { + raw = _prefs.getString(_historyKey); + } else { + final file = await _historyFile(); + if (await file.exists()) raw = await file.readAsString(); + } + history = raw == null + ? [] + : (jsonDecode(raw) as List) + .map((j) => DailyBriefing.fromJson(j as Map)) + .toList(); + } catch (_) { + history = []; + } + + // Earlier versions kept only the last briefing in preferences. + final legacy = _prefs.getString('last_briefing'); + if (legacy != null) { + try { + final briefing = DailyBriefing.fromJson(jsonDecode(legacy) as Map); + if (!history.any((b) => b.createdAt == briefing.createdAt)) history.add(briefing); + await saveBriefingHistory(history); + } catch (_) {} + await _prefs.remove('last_briefing'); + } + + history.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + return history; + } + + Future saveBriefingHistory(List history) async { + final json = jsonEncode(history.map((b) => b.toJson()).toList()); + if (kIsWeb) { + await _prefs.setString(_historyKey, json); + return; + } + // Write to a temp file and rename, so a crash mid-write can't corrupt it. + final file = await _historyFile(); + final temp = File('${file.path}.tmp'); + await temp.writeAsString(json, flush: true); + await temp.rename(file.path); + } + + static const _historyKey = 'briefing_history'; + + Future _historyFile() async => + File('${(await getApplicationSupportDirectory()).path}/briefings.json'); +} diff --git a/lib/services/summarizer.dart b/lib/services/summarizer.dart new file mode 100644 index 0000000..2a0760a --- /dev/null +++ b/lib/services/summarizer.dart @@ -0,0 +1,88 @@ +import '../models/article.dart'; +import '../models/briefing.dart'; +import '../models/news_source.dart'; + +class SummarizerException implements Exception { + SummarizerException(this.message); + + final String message; + + @override + String toString() => message; +} + +/// Something that can turn articles into summaries. Implemented by the +/// direct Claude API client and by the n8n webhook bridge. +abstract class Summarizer { + String get name; + + Future summarizeArticle(Article article, String text, SourceLanguage language); + + Future createBriefing(List
articles, SourceLanguage language); +} + +/// Shared prompt material and response mapping for all summarizers. +class BriefingInput { + BriefingInput(List
articles) + : byId = {for (var i = 0; i < articles.length; i++) 'a${i + 1}': articles[i]}; + + /// Short ids keep the prompt compact and are easy for the model to cite. + final Map byId; + + static const excerptChars = 220; + + String toPromptList() { + final buffer = StringBuffer(); + byId.forEach((id, a) { + final excerpt = a.excerpt.length > excerptChars + ? '${a.excerpt.substring(0, excerptChars)}…' + : a.excerpt; + buffer.writeln('[$id] (${a.source.name}) ${a.title}' + '${excerpt.isEmpty ? '' : ' — $excerpt'}'); + }); + return buffer.toString(); + } + + List> toJsonList() => [ + for (final e in byId.entries) + { + 'id': e.key, + 'title': e.value.title, + 'url': e.value.link, + 'source': e.value.source.name, + 'language': e.value.source.language.name, + 'published': e.value.published?.toIso8601String(), + 'excerpt': e.value.excerpt, + } + ]; + + /// Maps `{intro, topics: [{title, summary, article_ids}]}` onto a briefing. + DailyBriefing toBriefing(Map json, String generatedBy) { + final topics = (json['topics'] as List? ?? []).map((t) { + final topic = t as Map; + final ids = (topic['article_ids'] as List? ?? []).cast(); + return BriefingTopic( + title: topic['title'] as String? ?? '', + summary: topic['summary'] as String? ?? '', + sources: [ + for (final id in ids) + if (byId[id] != null) + ArticleRef( + title: byId[id]!.title, + url: byId[id]!.link, + sourceName: byId[id]!.source.name, + ), + ], + ); + }).toList(); + return DailyBriefing( + createdAt: DateTime.now(), + intro: json['intro'] as String? ?? '', + topics: topics, + generatedBy: generatedBy, + ); + } +} + +String languageName(SourceLanguage language) => + language == SourceLanguage.de ? 'German' : 'English'; diff --git a/lib/services/web_dark_mode.dart b/lib/services/web_dark_mode.dart new file mode 100644 index 0000000..ef2d74d --- /dev/null +++ b/lib/services/web_dark_mode.dart @@ -0,0 +1,26 @@ +/// Dark mode for the in-app "original page" view. +/// +/// When the phone itself is in dark mode, Android darkens pages on its own +/// (algorithmic darkening, plus `prefers-color-scheme: dark` for sites with a +/// dark design). When only the app is set to dark, Android doesn't, so this +/// stylesheet inverts the page and flips photos and videos back. +class WebDarkMode { + static const _css = 'html{filter:invert(1) hue-rotate(180deg)!important;' + 'background:#fff!important}' + 'img,picture,video,canvas,iframe,svg image,[style*="background-image"]' + '{filter:invert(1) hue-rotate(180deg)!important}'; + + static const script = ''' +(function () { + function apply() { + if (document.getElementById('tagesbyte-dark')) return; + var style = document.createElement('style'); + style.id = 'tagesbyte-dark'; + style.textContent = '$_css'; + (document.head || document.documentElement).appendChild(style); + } + apply(); + document.addEventListener('DOMContentLoaded', apply); +})(); +'''; +} diff --git a/lib/state/app_state.dart b/lib/state/app_state.dart new file mode 100644 index 0000000..ae25723 --- /dev/null +++ b/lib/state/app_state.dart @@ -0,0 +1,267 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +import '../data/default_sources.dart'; +import '../models/app_settings.dart'; +import '../models/article.dart'; +import '../models/briefing.dart'; +import '../models/news_source.dart'; +import '../services/article_extractor.dart'; +import '../services/claude_summarizer.dart'; +import '../services/consent_service.dart'; +import '../services/feed_service.dart'; +import '../services/n8n_summarizer.dart'; +import '../services/paywall_service.dart'; +import '../services/storage.dart'; +import '../services/summarizer.dart'; + +/// The result of an on-demand article summary. +class SummaryState { + const SummaryState.loading() : text = null, error = null; + const SummaryState.done(this.text) : error = null; + const SummaryState.failed(this.error) : text = null; + + final String? text; + final String? error; + + bool get isLoading => text == null && error == null; +} + +class AppState extends ChangeNotifier { + AppState(this._storage, {FeedService? feedService, ArticleExtractor? extractor}) + : _feeds = feedService ?? FeedService(), + consent = ConsentService(), + paywall = PaywallService(_storage) { + _extractor = extractor ?? ArticleExtractor(consent: consent); + } + + final Storage _storage; + final FeedService _feeds; + late final ArticleExtractor _extractor; + final PaywallService paywall; + + /// Consent cookies for sites with consent walls, shared with the web view. + final ConsentService consent; + + AppSettings settings = const AppSettings(); + List customSources = []; + Set enabledSourceIds = {}; + + /// Everything the feeds returned; see [visibleArticles] for what is shown. + List
articles = []; + Map feedErrors = {}; + bool loading = false; + DateTime? lastRefresh; + + /// Saved briefings, newest first. + List briefings = []; + DailyBriefing? get briefing => briefings.firstOrNull; + bool briefingLoading = false; + String? briefingError; + + final Map summaries = {}; + + /// Reader content per article id, shared by the reader view and the AI. + final Map> _content = {}; + + bool _isHidden(Article a) => + (settings.hidePaid && paywall.isPaid(a)) || + (settings.hideSponsored && paywall.isSponsored(a)); + + /// Articles after the paid/sponsored filters. The feed and the briefing use these. + List
get visibleArticles => articles.where((a) => !_isHidden(a)).toList(); + + int get hiddenCount => articles.where(_isHidden).length; + + List get allSources => [...defaultSources, ...customSources]; + + List get enabledSources => + allSources.where((s) => enabledSourceIds.contains(s.id)).toList(); + + Future init() async { + settings = await _storage.loadSettings(); + customSources = _storage.loadCustomSources(); + enabledSourceIds = _storage.loadEnabledSourceIds() ?? + defaultSources.where((s) => s.enabledByDefault).map((s) => s.id).toSet(); + briefings = await _storage.loadBriefingHistory(); + notifyListeners(); + await refresh(); + } + + Future refresh() async { + if (loading) return; + loading = true; + notifyListeners(); + final result = await _feeds.fetchAll( + enabledSources, + maxAge: Duration(hours: settings.maxAgeHours), + corsProxy: settings.corsProxy, + ); + articles = result.articles; + feedErrors = result.errors; + lastRefresh = DateTime.now(); + loading = false; + notifyListeners(); + // Runs in the background; hides newly found paid articles as they turn up. + unawaited(paywall.checkPages(articles).then((foundPaid) { + if (foundPaid) notifyListeners(); + })); + } + + // --- Sources ------------------------------------------------------------- + + Future setSourceEnabled(NewsSource source, bool enabled) async { + enabled ? enabledSourceIds.add(source.id) : enabledSourceIds.remove(source.id); + notifyListeners(); + await _storage.saveEnabledSourceIds(enabledSourceIds); + } + + /// Validates the feed by fetching it before adding. Returns an error message + /// or null on success. + Future addCustomSource({ + required String name, + required String feedUrl, + required SourceLanguage language, + }) async { + final uri = Uri.tryParse(feedUrl); + final source = NewsSource( + id: 'custom-${DateTime.now().millisecondsSinceEpoch}', + name: name, + feedUrl: feedUrl, + homepage: uri == null ? '' : '${uri.scheme}://${uri.host}', + language: language, + isCustom: true, + ); + try { + final items = await _feeds.fetchSource(source, corsProxy: settings.corsProxy); + if (items.isEmpty) return 'The feed loaded but contains no articles.'; + } catch (e) { + return 'Could not load feed: $e'; + } + customSources = [...customSources, source]; + enabledSourceIds.add(source.id); + await _storage.saveCustomSources(customSources); + await _storage.saveEnabledSourceIds(enabledSourceIds); + notifyListeners(); + return null; + } + + Future removeCustomSource(NewsSource source) async { + customSources = customSources.where((s) => s.id != source.id).toList(); + enabledSourceIds.remove(source.id); + await _storage.saveCustomSources(customSources); + await _storage.saveEnabledSourceIds(enabledSourceIds); + notifyListeners(); + } + + // --- Settings ------------------------------------------------------------ + + Future updateSettings(AppSettings updated) async { + final previous = settings; + final windowChanged = updated.maxAgeHours != settings.maxAgeHours || + updated.corsProxy != settings.corsProxy; + final aiChanged = updated.aiProvider != settings.aiProvider || + updated.claudeModel != settings.claudeModel || + updated.summaryLanguage != settings.summaryLanguage; + settings = updated; + if (aiChanged) summaries.clear(); + // Cached reader content may be a consent-wall teaser; reload it. + if (updated.acceptConsentWalls != previous.acceptConsentWalls) _content.clear(); + notifyListeners(); + await _storage.saveSettings(updated); + if (windowChanged) await refresh(); + } + + // --- Reading ------------------------------------------------------------- + + Future loadArticle(Article article) => _content.putIfAbsent( + article.id, + () => _extractor.load(article, + corsProxy: settings.corsProxy, acceptConsentWalls: settings.acceptConsentWalls)); + + /// Finds a loaded article by URL, or builds a minimal one (e.g. for links in + /// an older briefing whose articles have dropped out of the feed). + Article articleFor({required String url, required String title, required String sourceName}) { + for (final a in articles) { + if (a.link == url) return a; + } + final source = allSources.firstWhere( + (s) => s.name == sourceName, + orElse: () => NewsSource( + id: sourceName, + name: sourceName, + feedUrl: '', + homepage: '', + language: SourceLanguage.en, + ), + ); + return Article(title: title, link: url, source: source); + } + + // --- AI ------------------------------------------------------------------ + + Summarizer? get _summarizer => switch (settings.aiProvider) { + _ when !settings.aiConfigured => null, + AiProvider.claude => ClaudeSummarizer( + apiKey: settings.claudeApiKey, model: settings.claudeModel), + AiProvider.n8n => N8nSummarizer(webhookUrl: settings.n8nWebhookUrl), + AiProvider.none => null, + }; + + Future summarize(Article article) async { + final summarizer = _summarizer; + if (summarizer == null) return; + summaries[article.id] = const SummaryState.loading(); + notifyListeners(); + try { + final content = await loadArticle(article); + final text = content.plainText.length > article.excerpt.length + ? content.plainText + : article.excerpt; + final summary = + await summarizer.summarizeArticle(article, text, settings.summaryLanguage); + summaries[article.id] = SummaryState.done(summary); + } catch (e) { + summaries[article.id] = SummaryState.failed(e.toString()); + } + notifyListeners(); + } + + Future deleteBriefing(DailyBriefing briefing) async { + briefings = briefings.where((b) => !identical(b, briefing)).toList(); + notifyListeners(); + await _storage.saveBriefingHistory(briefings); + } + + /// Puts a deleted briefing back (undo). + Future restoreBriefing(DailyBriefing briefing) async { + briefings = [...briefings, briefing] + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + notifyListeners(); + await _storage.saveBriefingHistory(briefings); + } + + Future generateBriefing() async { + final summarizer = _summarizer; + if (summarizer == null || briefingLoading) return; + final articles = visibleArticles; + if (articles.isEmpty) { + briefingError = 'No articles loaded yet. Pull to refresh the feed first.'; + notifyListeners(); + return; + } + briefingLoading = true; + briefingError = null; + notifyListeners(); + try { + final created = await summarizer.createBriefing(articles, settings.summaryLanguage); + briefings = [created, ...briefings]; + await _storage.saveBriefingHistory(briefings); + } catch (e) { + briefingError = e.toString(); + } + briefingLoading = false; + notifyListeners(); + } +} diff --git a/lib/ui/article_screen.dart b/lib/ui/article_screen.dart new file mode 100644 index 0000000..65dbbb9 --- /dev/null +++ b/lib/ui/article_screen.dart @@ -0,0 +1,546 @@ +import 'dart:collection'; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + +import '../models/article.dart'; +import '../services/article_extractor.dart'; +import '../services/consent_service.dart'; +import '../services/content_blocking.dart'; +import '../services/web_dark_mode.dart'; +import '../state/app_state.dart'; +import 'common.dart'; + +Future openArticle(BuildContext context, AppState state, Article article) { + // Otherwise the previous screen restores its focus (and keyboard) on return. + FocusManager.instance.primaryFocus?.unfocus(); + return Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ArticleScreen(state: state, article: article), + )); +} + +enum _Mode { reader, original } + +/// Shows an article inside the app: a clean reader view by default, or the +/// original page in a WebView. +class ArticleScreen extends StatefulWidget { + const ArticleScreen({super.key, required this.state, required this.article}); + + final AppState state; + final Article article; + + @override + State createState() => _ArticleScreenState(); +} + +class _ArticleScreenState extends State { + late final Future _content = widget.state.loadArticle(widget.article); + _Mode _mode = _Mode.reader; + bool _webStarted = false; + InAppWebViewController? _web; + int _webProgress = 0; + + /// Only Android, iOS and macOS are set up; Windows would need WebView2 tooling. + static bool get webViewSupported => + !kIsWeb && + (defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS); + + /// Set when the user chooses "Open anyway" on a consent wall. + bool _allowConsentWall = false; + bool _stoppedAtWall = false; + bool _triedAccepting = false; + + Future _showOriginal() async { + if (!webViewSupported) { + openUrl(context, widget.article.link); + return; + } + // Reader view usually accepted the wall already; give the page its cookies. + await _syncConsentCookies(); + if (_stoppedAtWall) { + _stoppedAtWall = false; + _web?.loadUrl(urlRequest: URLRequest(url: WebUri(widget.article.link))); + } + if (!mounted) return; + setState(() { + _webStarted = true; + _mode = _Mode.original; + }); + } + + Future _syncConsentCookies() async { + final url = Uri.parse(widget.article.link); + final cookies = widget.state.consent.cookiesFor(url); + // Consent from Golem's simple button is valid for a day. + final expires = DateTime.now().add(const Duration(days: 1)).millisecondsSinceEpoch; + for (final cookie in cookies.entries) { + await CookieManager.instance().setCookie( + url: WebUri(widget.article.link), + name: cookie.key, + value: cookie.value, + domain: '.${ConsentService.domainOf(url)}', + isSecure: true, + expiresDate: expires, + ); + } + } + + void _openAnyway() { + _allowConsentWall = true; + _showOriginal(); + } + + Future _checkConsentWall(InAppWebViewController controller, WebUri? url) async { + if (url == null || + _stoppedAtWall || + _allowConsentWall || + !ContentBlocking.consentWall.hasMatch(url.toString())) { + return; + } + final settings = widget.state.settings; + final consent = widget.state.consent; + if (settings.acceptConsentWalls && !_triedAccepting && consent.canAccept(url)) { + _triedAccepting = true; + await controller.stopLoading(); + if (await consent.accept(url)) { + await _syncConsentCookies(); + await controller.loadUrl(urlRequest: URLRequest(url: WebUri(widget.article.link))); + return; + } + } + if (!settings.hideCookieBanners) return; + await controller.stopLoading(); + _stoppedAtWall = true; + if (!mounted) return; + setState(() => _mode = _Mode.reader); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: const Duration(seconds: 8), + content: Text('${widget.article.source.name} only opens its website after you accept ' + 'tracking or subscribe. Showing reader view instead.'), + action: SnackBarAction( + label: 'Open anyway', + onPressed: _openAnyway, + ), + )); + } + + Widget _buildWebView() { + final settings = widget.state.settings; + final script = ContentBlocking.cosmeticScript( + ads: settings.blockAds, cookies: settings.hideCookieBanners); + final appDark = Theme.of(context).brightness == Brightness.dark; + final systemDark = MediaQuery.platformBrightnessOf(context) == Brightness.dark; + return InAppWebView( + initialUrlRequest: URLRequest(url: WebUri(widget.article.link)), + initialSettings: InAppWebViewSettings( + contentBlockers: ContentBlocking.blockers( + ads: settings.blockAds, cookies: settings.hideCookieBanners), + // Only takes effect when the phone is in dark mode; see WebDarkMode. + algorithmicDarkeningAllowed: appDark, + ), + initialUserScripts: UnmodifiableListView([ + if (script != null) + UserScript(source: script, injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START), + if (appDark && !systemDark) + UserScript( + source: WebDarkMode.script, + injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START), + ]), + onWebViewCreated: (controller) => _web = controller, + // Redirects don't reliably fire every callback, so check them all. + onLoadStart: _checkConsentWall, + onLoadStop: _checkConsentWall, + onUpdateVisitedHistory: (controller, url, _) => _checkConsentWall(controller, url), + onProgressChanged: (_, progress) { + if (mounted) setState(() => _webProgress = progress); + }, + ); + } + + Future _handleBack() async { + // In the original page, back walks the page's own history first. + final web = _web; + if (_mode == _Mode.original && web != null && await web.canGoBack()) { + await web.goBack(); + } else if (mounted) { + Navigator.of(context).pop(); + } + } + + @override + Widget build(BuildContext context) { + final article = widget.article; + final original = _mode == _Mode.original; + return PopScope( + canPop: !original, + onPopInvokedWithResult: (didPop, _) { + if (!didPop) _handleBack(); + }, + child: Scaffold( + appBar: AppBar( + title: Text(article.source.name), + actions: [ + if (webViewSupported) + IconButton( + tooltip: original ? 'Reader view' : 'Original page', + icon: Icon(original ? Icons.article_outlined : Icons.public), + onPressed: original + ? () => setState(() => _mode = _Mode.reader) + : _showOriginal, + ), + PopupMenuButton( + onSelected: (_) => openUrl(context, article.link), + itemBuilder: (_) => const [ + PopupMenuItem(value: 'browser', child: Text('Open in browser')), + ], + ), + ], + bottom: PreferredSize( + preferredSize: const Size.fromHeight(2), + child: original && _webProgress < 100 + ? LinearProgressIndicator(value: _webProgress / 100, minHeight: 2) + : const SizedBox(height: 2), + ), + ), + // IndexedStack keeps the reader's scroll position and the loaded page + // when switching back and forth. + body: IndexedStack( + index: original ? 1 : 0, + children: [ + _ReaderView( + state: widget.state, + article: article, + content: _content, + onShowOriginal: _showOriginal, + onOpenAnyway: _openAnyway, + ), + if (_webStarted) _buildWebView() else const SizedBox(), + ], + ), + floatingActionButton: original && widget.state.settings.aiConfigured + ? FloatingActionButton.extended( + onPressed: () => showModalBottomSheet( + context: context, + showDragHandle: true, + isScrollControlled: true, + builder: (_) => SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(20, 0, 20, 24), + child: AiSummaryCard(state: widget.state, article: article), + ), + ), + ), + icon: const Icon(Icons.auto_awesome), + label: const Text('AI summary'), + ) + : null, + ), + ); + } +} + +class _ReaderView extends StatelessWidget { + const _ReaderView({ + required this.state, + required this.article, + required this.content, + required this.onShowOriginal, + required this.onOpenAnyway, + }); + + final AppState state; + final Article article; + final Future content; + final VoidCallback onShowOriginal; + + /// Opens the original page even if it is a consent wall. + final VoidCallback onOpenAnyway; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final muted = theme.colorScheme.onSurfaceVariant; + final meta = [ + if (article.published != null) formatDateTime(article.published!), + if (article.author != null) article.author!, + ].join(' · '); + + return SelectionArea( + child: FutureBuilder( + future: content, + builder: (context, snapshot) { + final loaded = snapshot.data; + var blocks = loaded?.blocks ?? const []; + // The page's own hero image duplicates the feed image shown on top. + if (article.imageUrl != null) { + blocks = blocks.skipWhile((b) => b.type == BlockType.image).toList(); + } + final isFull = loaded != null && + loaded.plainText.length >= max(400, article.excerpt.length * 2); + // Skip the lead if the body already starts with it. + final firstText = blocks.where((b) => b.type == BlockType.paragraph).firstOrNull; + final showLead = article.excerpt.isNotEmpty && + (firstText == null || + !firstText.text.startsWith( + article.excerpt.substring(0, min(40, article.excerpt.length)))); + + return ListView( + padding: const EdgeInsets.only(bottom: 48), + children: [ + if (article.imageUrl != null) + Image.network( + article.imageUrl!, + fit: BoxFit.cover, + webHtmlElementStrategy: WebHtmlElementStrategy.prefer, + // Take up space only once the image has loaded, so a broken + // image leaves no gap. + frameBuilder: (_, child, frame, __) => frame == null + ? const SizedBox.shrink() + : AspectRatio(aspectRatio: 16 / 9, child: child), + errorBuilder: (_, __, ___) => const SizedBox.shrink(), + ), + Padding( + padding: const EdgeInsets.fromLTRB(20, 20, 20, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (meta.isNotEmpty) + Text(meta, + style: theme.textTheme.labelMedium + ?.copyWith(color: theme.colorScheme.primary)), + const SizedBox(height: 8), + Text(article.title, style: theme.textTheme.headlineSmall), + if (showLead) ...[ + const SizedBox(height: 12), + Text(article.excerpt, + style: theme.textTheme.titleMedium + ?.copyWith(color: muted, height: 1.5)), + ], + const SizedBox(height: 20), + AiSummaryCard(state: state, article: article), + const SizedBox(height: 20), + if (loaded == null) + const Padding( + padding: EdgeInsets.symmetric(vertical: 32), + child: Center(child: CircularProgressIndicator()), + ) + else if (!isFull) + _TeaserOnlyNotice( + sourceName: article.source.name, + consentWall: loaded.consentWall, + onShowOriginal: loaded.consentWall ? onOpenAnyway : onShowOriginal, + ), + if (isFull) + for (final block in blocks) + if (block.text != article.imageUrl) _BlockView(block: block), + if (loaded != null) ...[ + const SizedBox(height: 24), + const Divider(), + TextButton.icon( + onPressed: onShowOriginal, + icon: const Icon(Icons.public, size: 18), + label: Text( + 'Original article on ${Uri.parse(article.link).host.replaceFirst('www.', '')}'), + ), + ], + ], + ), + ), + ], + ); + }, + ), + ); + } +} + +class _BlockView extends StatelessWidget { + const _BlockView({required this.block}); + + final ReaderBlock block; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final body = theme.textTheme.bodyLarge?.copyWith(height: 1.65); + return switch (block.type) { + BlockType.heading => Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8), + child: Text(block.text, + style: theme.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600)), + ), + BlockType.paragraph => Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text(block.text, style: body), + ), + BlockType.listItem => Padding( + padding: const EdgeInsets.only(bottom: 8, left: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('• ', style: body), + Expanded(child: Text(block.text, style: body)), + ], + ), + ), + BlockType.quote => Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.only(left: 16), + decoration: BoxDecoration( + border: Border(left: BorderSide(color: theme.colorScheme.primary, width: 3)), + ), + child: Text(block.text, style: body?.copyWith(fontStyle: FontStyle.italic)), + ), + BlockType.code => Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(12), + width: double.infinity, + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text(block.text, + style: const TextStyle(fontFamily: 'monospace', fontSize: 13)), + ), + ), + BlockType.image => Padding( + padding: const EdgeInsets.only(bottom: 8, top: 4), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + block.text, + width: double.infinity, + fit: BoxFit.fitWidth, + webHtmlElementStrategy: WebHtmlElementStrategy.prefer, + errorBuilder: (_, __, ___) => const SizedBox(), + ), + ), + ), + BlockType.caption => Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text(block.text, + style: theme.textTheme.bodySmall + ?.copyWith(color: theme.colorScheme.onSurfaceVariant)), + ), + }; + } +} + +class _TeaserOnlyNotice extends StatelessWidget { + const _TeaserOnlyNotice({ + required this.sourceName, + required this.consentWall, + required this.onShowOriginal, + }); + + final String sourceName; + final bool consentWall; + final VoidCallback onShowOriginal; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + consentWall + ? '$sourceName only shows its articles after you accept tracking or ' + 'subscribe, so only the teaser is available here.' + : "The full text couldn't be loaded in reader view. It may be behind a " + 'paywall or the site blocks it.', + style: TextStyle(color: theme.colorScheme.onSecondaryContainer), + ), + const SizedBox(height: 8), + FilledButton.tonalIcon( + onPressed: onShowOriginal, + icon: const Icon(Icons.public), + label: Text(consentWall ? 'Open website anyway' : 'Show original page'), + ), + ], + ), + ); + } +} + +/// The "Summarize with AI" button and its result. +class AiSummaryCard extends StatelessWidget { + const AiSummaryCard({super.key, required this.state, required this.article}); + + final AppState state; + final Article article; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: state, + builder: (context, _) { + final theme = Theme.of(context); + final summary = state.summaries[article.id]; + + if (!state.settings.aiConfigured) { + return Text( + 'Tip: set up Claude or your n8n workflow in Settings to get AI summaries.', + style: theme.textTheme.bodySmall + ?.copyWith(color: theme.colorScheme.onSurfaceVariant), + ); + } + if (summary == null) { + return FilledButton.tonalIcon( + onPressed: () => state.summarize(article), + icon: const Icon(Icons.auto_awesome), + label: const Text('Summarize with AI'), + ); + } + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.auto_awesome, size: 18, color: theme.colorScheme.tertiary), + const SizedBox(width: 8), + Text('AI summary', style: theme.textTheme.titleSmall), + ], + ), + const SizedBox(height: 12), + if (summary.isLoading) + const Row(children: [ + SizedBox( + width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)), + SizedBox(width: 12), + Text('Reading the article…'), + ]) + else if (summary.error != null) ...[ + Text(summary.error!, style: TextStyle(color: theme.colorScheme.error)), + TextButton( + onPressed: () => state.summarize(article), child: const Text('Try again')), + ] else + Text(summary.text!, + style: theme.textTheme.bodyMedium?.copyWith(height: 1.5)), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/briefing_history_screen.dart b/lib/ui/briefing_history_screen.dart new file mode 100644 index 0000000..f7403eb --- /dev/null +++ b/lib/ui/briefing_history_screen.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; + +import '../models/briefing.dart'; +import '../state/app_state.dart'; +import 'briefing_screen.dart'; +import 'common.dart'; + +/// All saved briefings, newest first, grouped by month. +class BriefingHistoryScreen extends StatelessWidget { + const BriefingHistoryScreen({super.key, required this.state}); + + final AppState state; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: state, + builder: (context, _) { + final briefings = state.briefings; + return Scaffold( + appBar: AppBar( + title: const Text('Briefing history'), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(24), + child: Container( + alignment: AlignmentDirectional.centerStart, + padding: const EdgeInsetsDirectional.only(start: 16, bottom: 8), + child: Text( + '${briefings.length} saved · swipe left to delete', + style: Theme.of(context).textTheme.bodySmall, + ), + ), + ), + ), + body: briefings.isEmpty + ? const EmptyState( + icon: Icons.history, + title: 'No briefings yet', + message: 'Every briefing you generate is saved here.', + ) + : ListView( + padding: const EdgeInsets.only(bottom: 24), + children: [ + for (final (i, briefing) in briefings.indexed) ...[ + if (i == 0 || !_sameMonth(briefings[i - 1].createdAt, briefing.createdAt)) + _MonthHeader(date: briefing.createdAt), + _HistoryTile(state: state, briefing: briefing, isLatest: i == 0), + ], + ], + ), + ); + }, + ); + } + + static bool _sameMonth(DateTime a, DateTime b) { + final la = a.toLocal(), lb = b.toLocal(); + return la.year == lb.year && la.month == lb.month; + } +} + +class _MonthHeader extends StatelessWidget { + const _MonthHeader({required this.date}); + + final DateTime date; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final d = date.toLocal(); + return Padding( + padding: const EdgeInsets.fromLTRB(16, 20, 16, 4), + child: Text('${monthNames[d.month - 1]} ${d.year}', + style: theme.textTheme.titleSmall?.copyWith(color: theme.colorScheme.primary)), + ); + } +} + +class _HistoryTile extends StatelessWidget { + const _HistoryTile({required this.state, required this.briefing, required this.isLatest}); + + final AppState state; + final DailyBriefing briefing; + final bool isLatest; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final d = briefing.createdAt.toLocal(); + final time = '${d.hour.toString().padLeft(2, '0')}:${d.minute.toString().padLeft(2, '0')}'; + return Dismissible( + key: ObjectKey(briefing), + direction: DismissDirection.endToStart, + background: Container( + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 24), + color: theme.colorScheme.errorContainer, + child: Icon(Icons.delete_outline, color: theme.colorScheme.onErrorContainer), + ), + onDismissed: (_) { + state.deleteBriefing(briefing); + final messenger = ScaffoldMessenger.of(context); + messenger.hideCurrentSnackBar(); + messenger.showSnackBar(SnackBar( + content: Text('Briefing from ${formatDateTime(briefing.createdAt)} deleted'), + action: SnackBarAction( + label: 'Undo', + onPressed: () => state.restoreBriefing(briefing), + ), + )); + }, + child: ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + leading: _DateBadge(date: d), + title: Text( + '${weekdayNames[d.weekday - 1]} · $time${isLatest ? ' · latest' : ''}', + style: theme.textTheme.titleSmall, + ), + subtitle: Text( + briefing.intro.isNotEmpty + ? briefing.intro + : briefing.topics.map((t) => t.title).join(' · '), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + trailing: briefing.topics.isEmpty + ? null + : Text( + '${briefing.topics.length}\n${briefing.topics.length == 1 ? 'topic' : 'topics'}', + textAlign: TextAlign.center, + style: theme.textTheme.labelSmall + ?.copyWith(color: theme.colorScheme.onSurfaceVariant)), + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (_) => BriefingDetailScreen(state: state, briefing: briefing), + )), + ), + ); + } +} + +class _DateBadge extends StatelessWidget { + const _DateBadge({required this.date}); + + final DateTime date; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + width: 48, + padding: const EdgeInsets.symmetric(vertical: 6), + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('${date.day}', + style: theme.textTheme.titleMedium?.copyWith( + color: theme.colorScheme.onPrimaryContainer, fontWeight: FontWeight.w600)), + Text(monthNames[date.month - 1].substring(0, 3), + style: theme.textTheme.labelSmall + ?.copyWith(color: theme.colorScheme.onPrimaryContainer)), + ], + ), + ); + } +} + +/// One saved briefing, read-only. +class BriefingDetailScreen extends StatelessWidget { + const BriefingDetailScreen({super.key, required this.state, required this.briefing}); + + final AppState state; + final DailyBriefing briefing; + + @override + Widget build(BuildContext context) { + final d = briefing.createdAt.toLocal(); + return Scaffold( + appBar: AppBar( + title: Text( + '${weekdayNames[d.weekday - 1]}, ${d.day} ${monthNames[d.month - 1]} ${d.year}')), + body: ListView( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 32), + children: briefingContent(context, state, briefing), + ), + ); + } +} diff --git a/lib/ui/briefing_screen.dart b/lib/ui/briefing_screen.dart new file mode 100644 index 0000000..f9a01aa --- /dev/null +++ b/lib/ui/briefing_screen.dart @@ -0,0 +1,211 @@ +import 'package:flutter/material.dart'; + +import '../models/briefing.dart'; +import '../state/app_state.dart'; +import 'article_screen.dart'; +import 'briefing_history_screen.dart'; +import 'common.dart'; + +class BriefingScreen extends StatelessWidget { + const BriefingScreen({super.key, required this.state, required this.openSettings}); + + final AppState state; + final VoidCallback openSettings; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: state, + builder: (context, _) { + final briefing = state.briefing; + final canGenerate = + state.settings.aiConfigured && !state.briefingLoading && !state.loading; + return Scaffold( + appBar: AppBar( + title: const Text('Daily Briefing'), + actions: [ + if (state.briefings.isNotEmpty) + IconButton( + tooltip: 'History', + icon: const Icon(Icons.history), + onPressed: () => Navigator.of(context).push(MaterialPageRoute( + builder: (_) => BriefingHistoryScreen(state: state), + )), + ), + if (briefing != null && state.settings.aiConfigured) + IconButton( + tooltip: 'Regenerate', + onPressed: canGenerate ? state.generateBriefing : null, + icon: const Icon(Icons.refresh), + ), + ], + ), + body: _body(context, briefing, canGenerate), + ); + }, + ); + } + + Widget _body(BuildContext context, DailyBriefing? briefing, bool canGenerate) { + if (!state.settings.aiConfigured) { + return EmptyState( + icon: Icons.auto_awesome, + title: 'AI is not set up yet', + message: 'Add a Claude API key or your n8n webhook to get a daily briefing ' + 'of the most important stories.', + action: FilledButton(onPressed: openSettings, child: const Text('Open Settings')), + ); + } + if (state.briefingLoading) { + return EmptyState( + icon: Icons.hourglass_top, + title: 'Reading ${state.visibleArticles.length} headlines…', + message: 'This usually takes 10-60 seconds.', + action: const SizedBox(width: 160, child: LinearProgressIndicator()), + ); + } + final generateButton = FilledButton.icon( + onPressed: canGenerate ? state.generateBriefing : null, + icon: const Icon(Icons.auto_awesome), + label: Text(state.loading + ? 'Waiting for feeds…' + : 'Generate from ${state.visibleArticles.length} articles'), + ); + if (briefing == null) { + return EmptyState( + icon: Icons.summarize_outlined, + title: 'No briefing yet', + message: state.briefingError ?? + 'Let the AI pick and summarize the most important stories of the day.', + action: generateButton, + ); + } + + final theme = Theme.of(context); + final outdated = !isSameDay(briefing.createdAt, DateTime.now()); + return ListView( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 32), + children: [ + if (state.briefingError != null) + _Notice(text: state.briefingError!, color: theme.colorScheme.errorContainer), + if (outdated) + _Notice( + text: 'This briefing is from ${formatDateTime(briefing.createdAt)}.', + color: theme.colorScheme.secondaryContainer, + action: TextButton( + onPressed: canGenerate ? state.generateBriefing : null, + child: const Text('Update')), + ), + ...briefingContent(context, state, briefing), + ], + ); + } +} + +/// Date line, intro and topic cards of a briefing. Shared by today's briefing +/// and the history. +List briefingContent(BuildContext context, AppState state, DailyBriefing briefing) { + final theme = Theme.of(context); + return [ + Text( + '${formatDateTime(briefing.createdAt)} · ${briefing.generatedBy}', + style: theme.textTheme.labelMedium?.copyWith(color: theme.colorScheme.primary), + ), + const SizedBox(height: 8), + Text(briefing.intro, style: theme.textTheme.titleMedium?.copyWith(height: 1.4)), + const SizedBox(height: 16), + for (final (i, topic) in briefing.topics.indexed) ...[ + _TopicCard(state: state, number: i + 1, topic: topic), + const SizedBox(height: 12), + ], + ]; +} + +class _TopicCard extends StatelessWidget { + const _TopicCard({required this.state, required this.number, required this.topic}); + + final AppState state; + final int number; + final BriefingTopic topic; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 14, + backgroundColor: theme.colorScheme.primaryContainer, + child: Text('$number', + style: theme.textTheme.labelLarge + ?.copyWith(color: theme.colorScheme.onPrimaryContainer)), + ), + const SizedBox(width: 12), + Expanded(child: Text(topic.title, style: theme.textTheme.titleMedium)), + ], + ), + const SizedBox(height: 10), + Text(topic.summary, style: theme.textTheme.bodyMedium?.copyWith(height: 1.5)), + if (topic.sources.isNotEmpty) ...[ + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 4, + children: [ + for (final ref in topic.sources) + Tooltip( + message: ref.title, + child: ActionChip( + avatar: const Icon(Icons.article_outlined, size: 16), + label: Text(ref.sourceName), + onPressed: () => openArticle( + context, + state, + state.articleFor( + url: ref.url, title: ref.title, sourceName: ref.sourceName), + ), + ), + ), + ], + ), + ], + ], + ), + ), + ); + } +} + +class _Notice extends StatelessWidget { + const _Notice({required this.text, required this.color, this.action}); + + final String text; + final Color color; + final Widget? action; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(8)), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Text(text), + )), + if (action != null) action!, + ], + ), + ); + } +} diff --git a/lib/ui/common.dart b/lib/ui/common.dart new file mode 100644 index 0000000..1cc0669 --- /dev/null +++ b/lib/ui/common.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// User-facing app name. Platform launcher labels (AndroidManifest, Info.plist, +/// web/manifest.json, windows/runner) are set separately. +const appName = 'Tagesbyte'; + +String timeAgo(DateTime? date) { + if (date == null) return ''; + final diff = DateTime.now().difference(date.toLocal()); + if (diff.inMinutes < 1) return 'just now'; + if (diff.inMinutes < 60) return '${diff.inMinutes} min ago'; + if (diff.inHours < 24) return '${diff.inHours} h ago'; + if (diff.inDays == 1) return 'yesterday'; + return '${diff.inDays} days ago'; +} + +String formatDateTime(DateTime date) { + final d = date.toLocal(); + String two(int n) => n.toString().padLeft(2, '0'); + return '${two(d.day)}.${two(d.month)}.${d.year} ${two(d.hour)}:${two(d.minute)}'; +} + +const weekdayNames = [ + 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', +]; + +const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December', +]; + +bool isSameDay(DateTime a, DateTime b) => + a.year == b.year && a.month == b.month && a.day == b.day; + +Future openUrl(BuildContext context, String url) async { + final ok = await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + if (!ok && context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text('Could not open $url'))); + } +} + +/// Centered icon + text + optional action, used for empty and error states. +class EmptyState extends StatelessWidget { + const EmptyState({ + super.key, + required this.icon, + required this.title, + this.message, + this.action, + }); + + final IconData icon; + final String title; + final String? message; + final Widget? action; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 56, color: theme.colorScheme.primary), + const SizedBox(height: 16), + Text(title, style: theme.textTheme.titleMedium, textAlign: TextAlign.center), + if (message != null) ...[ + const SizedBox(height: 8), + Text( + message!, + style: theme.textTheme.bodyMedium + ?.copyWith(color: theme.colorScheme.onSurfaceVariant), + textAlign: TextAlign.center, + ), + ], + if (action != null) ...[const SizedBox(height: 20), action!], + ], + ), + ), + ); + } +} diff --git a/lib/ui/feed_screen.dart b/lib/ui/feed_screen.dart new file mode 100644 index 0000000..2440006 --- /dev/null +++ b/lib/ui/feed_screen.dart @@ -0,0 +1,322 @@ +import 'package:flutter/material.dart'; + +import '../models/article.dart'; +import '../models/news_source.dart'; +import '../state/app_state.dart'; +import 'article_screen.dart'; +import 'common.dart'; + +class FeedScreen extends StatefulWidget { + const FeedScreen({super.key, required this.state}); + + final AppState state; + + @override + State createState() => _FeedScreenState(); +} + +class _FeedScreenState extends State { + SourceLanguage? _language; + String? _sourceId; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: widget.state, + builder: (context, _) { + final state = widget.state; + final all = state.visibleArticles; + final visible = all + .where((a) => _language == null || a.source.language == _language) + .where((a) => _sourceId == null || a.source.id == _sourceId) + .toList(); + final sourcesInFeed = {for (final a in all) a.source.id: a.source}; + final hidden = state.hiddenCount; + + return Scaffold( + appBar: AppBar( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text(appName), + Text( + state.loading + ? 'Loading feeds…' + : '${all.length} articles · last ${state.settings.maxAgeHours} h' + '${hidden > 0 ? ' · $hidden paid/ads hidden' : ''}', + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + actions: [ + IconButton( + tooltip: 'Refresh', + onPressed: state.loading ? null : state.refresh, + icon: const Icon(Icons.refresh), + ), + ], + bottom: PreferredSize( + preferredSize: const Size.fromHeight(52), + child: _filterBar(sourcesInFeed.values.toList()), + ), + ), + body: Column( + children: [ + if (state.loading) const LinearProgressIndicator(), + if (state.feedErrors.isNotEmpty && !state.loading) + _ErrorBanner(errors: state.feedErrors), + Expanded( + child: RefreshIndicator( + onRefresh: state.refresh, + child: _list(state, visible), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _list(AppState state, List
visible) { + if (visible.isEmpty) { + // ListView keeps pull-to-refresh working on the empty state. + return ListView( + children: [ + const SizedBox(height: 80), + EmptyState( + icon: state.loading ? Icons.hourglass_top : Icons.inbox_outlined, + title: state.loading + ? 'Fetching the latest news…' + : state.enabledSources.isEmpty + ? 'No sources enabled' + : 'No articles found', + message: state.loading + ? null + : state.enabledSources.isEmpty + ? 'Turn on some feeds in the Sources tab.' + : 'Pull down to refresh or change the filter.', + ), + ], + ); + } + return ListView.separated( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 24), + itemCount: visible.length, + separatorBuilder: (_, __) => const SizedBox(height: 8), + itemBuilder: (context, i) => ArticleCard( + article: visible[i], + hasSummary: state.summaries[visible[i].id]?.text != null, + isPaid: state.paywall.isPaid(visible[i]), + onTap: () => openArticle(context, state, visible[i]), + ), + ); + } + + Widget _filterBar(List sources) { + sources.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + return SizedBox( + height: 52, + child: ListView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + children: [ + ChoiceChip( + label: const Text('All'), + selected: _language == null, + onSelected: (_) => setState(() => _language = null), + ), + for (final lang in SourceLanguage.values) ...[ + const SizedBox(width: 8), + ChoiceChip( + label: Text('${lang.flag} ${lang.label}'), + selected: _language == lang, + onSelected: (_) => setState(() => _language = lang), + ), + ], + const SizedBox(width: 8), + PopupMenuButton( + tooltip: 'Filter by source', + onSelected: (id) => setState(() => _sourceId = id), + itemBuilder: (_) => [ + const PopupMenuItem(value: null, child: Text('All sources')), + for (final s in sources) PopupMenuItem(value: s.id, child: Text(s.name)), + ], + child: Chip( + avatar: const Icon(Icons.filter_list, size: 18), + label: Text(_sourceId == null + ? 'All sources' + : sources + .firstWhere((s) => s.id == _sourceId, + orElse: () => sources.isEmpty ? _placeholder : sources.first) + .name), + ), + ), + ], + ), + ); + } + + static const _placeholder = NewsSource( + id: '', name: 'All sources', feedUrl: '', homepage: '', language: SourceLanguage.en); +} + +class ArticleCard extends StatelessWidget { + const ArticleCard({ + super.key, + required this.article, + required this.onTap, + this.hasSummary = false, + this.isPaid = false, + }); + + final Article article; + final VoidCallback onTap; + final bool hasSummary; + + /// Only shown when paid articles aren't hidden. + final bool isPaid; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final muted = theme.colorScheme.onSurfaceVariant; + return Card( + clipBehavior: Clip.antiAlias, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text(article.source.language.flag), + const SizedBox(width: 6), + Flexible( + child: Text( + article.source.name, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.primary, + fontWeight: FontWeight.w600), + ), + ), + if (article.published != null) + Text(' · ${timeAgo(article.published)}', + style: theme.textTheme.labelMedium?.copyWith(color: muted)), + if (isPaid) ...[ + const SizedBox(width: 6), + Tooltip( + message: 'Paid article', + child: Icon(Icons.lock_outline, size: 14, color: muted), + ), + ], + if (hasSummary) ...[ + const SizedBox(width: 6), + Icon(Icons.auto_awesome, size: 14, color: theme.colorScheme.tertiary), + ], + ], + ), + const SizedBox(height: 6), + Text( + article.title, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.titleSmall?.copyWith(height: 1.3), + ), + if (article.excerpt.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + article.excerpt, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodySmall?.copyWith(color: muted), + ), + ], + ], + ), + ), + if (article.imageUrl != null) ...[ + const SizedBox(width: 12), + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + article.imageUrl!, + width: 84, + height: 84, + fit: BoxFit.cover, + webHtmlElementStrategy: WebHtmlElementStrategy.prefer, + errorBuilder: (_, __, ___) => const SizedBox(width: 84, height: 84), + ), + ), + ], + ], + ), + ), + ), + ); + } +} + +class _ErrorBanner extends StatelessWidget { + const _ErrorBanner({required this.errors}); + + final Map errors; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Material( + color: theme.colorScheme.errorContainer, + child: InkWell( + onTap: () => showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Some feeds failed'), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final e in errors.entries) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text.rich(TextSpan(children: [ + TextSpan( + text: '${e.key}: ', + style: const TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: e.value), + ])), + ), + ], + ), + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK')), + ], + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + Icon(Icons.warning_amber, size: 18, color: theme.colorScheme.onErrorContainer), + const SizedBox(width: 8), + Expanded( + child: Text( + '${errors.length} feed(s) could not be loaded. Tap for details.', + style: TextStyle(color: theme.colorScheme.onErrorContainer), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/ui/home_shell.dart b/lib/ui/home_shell.dart new file mode 100644 index 0000000..386bcf3 --- /dev/null +++ b/lib/ui/home_shell.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; + +import '../state/app_state.dart'; +import 'briefing_screen.dart'; +import 'feed_screen.dart'; +import 'settings_screen.dart'; +import 'sources_screen.dart'; + +class HomeShell extends StatefulWidget { + const HomeShell({super.key, required this.state}); + + final AppState state; + + @override + State createState() => _HomeShellState(); +} + +class _HomeShellState extends State { + int _index = 0; + bool _sourcesChanged = false; + + void _select(int index) { + // A text field left focused in Settings would otherwise get focus back + // (and open the keyboard) when returning from an article. + FocusManager.instance.primaryFocus?.unfocus(); + // Source toggles only take effect on the next refresh; do it when the + // user leaves the Sources tab. + if (_index == 2 && index != 2 && _sourcesChanged) { + _sourcesChanged = false; + widget.state.refresh(); + } + setState(() => _index = index); + } + + @override + Widget build(BuildContext context) { + final state = widget.state; + return Scaffold( + body: IndexedStack( + index: _index, + children: [ + FeedScreen(state: state), + BriefingScreen(state: state, openSettings: () => _select(3)), + SourcesScreen(state: state, onChanged: () => _sourcesChanged = true), + SettingsScreen(state: state), + ].indexed + // Hidden tabs stay alive but must not hold or regain focus. + .map((e) => ExcludeFocus(excluding: e.$1 != _index, child: e.$2)) + .toList(), + ), + bottomNavigationBar: NavigationBar( + selectedIndex: _index, + onDestinationSelected: _select, + destinations: const [ + NavigationDestination( + icon: Icon(Icons.newspaper_outlined), + selectedIcon: Icon(Icons.newspaper), + label: 'Today'), + NavigationDestination( + icon: Icon(Icons.auto_awesome_outlined), + selectedIcon: Icon(Icons.auto_awesome), + label: 'Briefing'), + NavigationDestination( + icon: Icon(Icons.rss_feed_outlined), + selectedIcon: Icon(Icons.rss_feed), + label: 'Sources'), + NavigationDestination( + icon: Icon(Icons.settings_outlined), + selectedIcon: Icon(Icons.settings), + label: 'Settings'), + ], + ), + ); + } +} diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart new file mode 100644 index 0000000..ba1be46 --- /dev/null +++ b/lib/ui/settings_screen.dart @@ -0,0 +1,271 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../models/app_settings.dart'; +import '../models/news_source.dart'; +import '../state/app_state.dart'; +import 'common.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key, required this.state}); + + final AppState state; + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + late AppSettings _draft; + final _apiKey = TextEditingController(); + final _webhook = TextEditingController(); + final _proxy = TextEditingController(); + bool _showKey = false; + + @override + void initState() { + super.initState(); + _loadFrom(widget.state.settings); + // Settings load asynchronously at startup; pick them up once they arrive. + widget.state.addListener(_syncIfPristine); + } + + void _loadFrom(AppSettings s) { + _draft = s; + _apiKey.text = s.claudeApiKey; + _webhook.text = s.n8nWebhookUrl; + _proxy.text = s.corsProxy; + } + + bool _touched = false; + + void _syncIfPristine() { + if (!_touched && mounted) setState(() => _loadFrom(widget.state.settings)); + } + + @override + void dispose() { + widget.state.removeListener(_syncIfPristine); + _apiKey.dispose(); + _webhook.dispose(); + _proxy.dispose(); + super.dispose(); + } + + void _edit(AppSettings Function(AppSettings) change) { + setState(() { + _touched = true; + _draft = change(_draft); + }); + } + + Future _save() async { + await widget.state.updateSettings(_draft.copyWith( + claudeApiKey: _apiKey.text.trim(), + n8nWebhookUrl: _webhook.text.trim(), + corsProxy: _proxy.text.trim(), + )); + _touched = false; + if (mounted) { + FocusScope.of(context).unfocus(); + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar(content: Text('Settings saved'))); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + Widget header(String text) => Padding( + padding: const EdgeInsets.fromLTRB(0, 24, 0, 8), + child: Text(text, + style: theme.textTheme.titleSmall?.copyWith(color: theme.colorScheme.primary)), + ); + Widget hint(String text) => Padding( + padding: const EdgeInsets.only(top: 8), + child: Text(text, + style: theme.textTheme.bodySmall + ?.copyWith(color: theme.colorScheme.onSurfaceVariant)), + ); + + return Scaffold( + appBar: AppBar(title: const Text('Settings')), + floatingActionButton: FloatingActionButton.extended( + onPressed: _save, + icon: const Icon(Icons.save_outlined), + label: const Text('Save'), + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 96), + children: [ + header('Appearance'), + SegmentedButton( + segments: const [ + ButtonSegment( + value: ThemeMode.system, + icon: Icon(Icons.brightness_auto_outlined), + label: Text('System')), + ButtonSegment( + value: ThemeMode.light, + icon: Icon(Icons.light_mode_outlined), + label: Text('Light')), + ButtonSegment( + value: ThemeMode.dark, + icon: Icon(Icons.dark_mode_outlined), + label: Text('Dark')), + ], + selected: {_draft.themeMode}, + // Applies right away (no Save needed) so the change is visible. + onSelectionChanged: (s) { + setState(() => _draft = _draft.copyWith(themeMode: s.first)); + widget.state + .updateSettings(widget.state.settings.copyWith(themeMode: s.first)); + }, + ), + header('AI summaries'), + SegmentedButton( + segments: [ + for (final p in AiProvider.values) ButtonSegment(value: p, label: Text(p.label)), + ], + selected: {_draft.aiProvider}, + onSelectionChanged: (s) => _edit((d) => d.copyWith(aiProvider: s.first)), + ), + if (_draft.aiProvider == AiProvider.claude) ...[ + const SizedBox(height: 16), + TextField( + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + controller: _apiKey, + obscureText: !_showKey, + onChanged: (_) => _touched = true, + decoration: InputDecoration( + labelText: 'Claude API key', + hintText: 'sk-ant-…', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon(_showKey ? Icons.visibility_off : Icons.visibility), + onPressed: () => setState(() => _showKey = !_showKey), + ), + ), + ), + const SizedBox(height: 16), + DropdownButtonFormField( + value: claudeModels.containsKey(_draft.claudeModel) + ? _draft.claudeModel + : claudeModels.keys.first, + decoration: + const InputDecoration(labelText: 'Model', border: OutlineInputBorder()), + items: [ + for (final e in claudeModels.entries) + DropdownMenuItem(value: e.key, child: Text(e.value)), + ], + onChanged: (v) => _edit((d) => d.copyWith(claudeModel: v)), + ), + Row( + children: [ + Expanded( + child: hint('The key is stored in the device keystore and only sent ' + 'to api.anthropic.com.'), + ), + TextButton( + onPressed: () => + openUrl(context, 'https://console.anthropic.com/settings/keys'), + child: const Text('Get a key'), + ), + ], + ), + ], + if (_draft.aiProvider == AiProvider.n8n) ...[ + const SizedBox(height: 16), + TextField( + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + controller: _webhook, + keyboardType: TextInputType.url, + onChanged: (_) => _touched = true, + decoration: const InputDecoration( + labelText: 'n8n webhook URL', + hintText: 'https://n8n.example.com/webhook/tech-news', + border: OutlineInputBorder(), + ), + ), + hint('The app POSTs articles to this webhook and expects JSON back. ' + 'See README.md for the request/response format.'), + ], + header('Summary language'), + SegmentedButton( + segments: [ + for (final l in SourceLanguage.values) + ButtonSegment(value: l, label: Text('${l.flag} ${l.label}')), + ], + selected: {_draft.summaryLanguage}, + onSelectionChanged: (s) => _edit((d) => d.copyWith(summaryLanguage: s.first)), + ), + hint('Summaries are written in this language, whatever language the source uses.'), + header('Feed'), + SegmentedButton( + segments: const [ + ButtonSegment(value: 12, label: Text('12 h')), + ButtonSegment(value: 24, label: Text('24 h')), + ButtonSegment(value: 48, label: Text('48 h')), + ], + selected: {_draft.maxAgeHours}, + onSelectionChanged: (s) => _edit((d) => d.copyWith(maxAgeHours: s.first)), + ), + hint('Only show articles published within this time window.'), + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Hide paid articles'), + subtitle: const Text('heise+, SPIEGEL+, Golem Plus, t3n Pro'), + value: _draft.hidePaid, + onChanged: (v) => _edit((d) => d.copyWith(hidePaid: v)), + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Hide sponsored posts'), + subtitle: const Text('Posts marked as ads, e.g. "Anzeige:" on Golem'), + value: _draft.hideSponsored, + onChanged: (v) => _edit((d) => d.copyWith(hideSponsored: v)), + ), + header('Original page view'), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Block ads and trackers'), + value: _draft.blockAds, + onChanged: (v) => _edit((d) => d.copyWith(blockAds: v)), + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Hide cookie banners'), + subtitle: const Text('Nothing is accepted; sites treat it like a rejection'), + value: _draft.hideCookieBanners, + onChanged: (v) => _edit((d) => d.copyWith(hideCookieBanners: v)), + ), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Accept cookie walls'), + subtitle: const Text('For sites that only show articles after you accept ' + '(Golem). Ads and trackers stay blocked.'), + value: _draft.acceptConsentWalls, + onChanged: (v) => _edit((d) => d.copyWith(acceptConsentWalls: v)), + ), + if (kIsWeb) ...[ + header('Web (CORS proxy)'), + TextField( + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), + controller: _proxy, + keyboardType: TextInputType.url, + onChanged: (_) => _touched = true, + decoration: const InputDecoration( + labelText: 'Proxy prefix', + hintText: 'https://corsproxy.example/?url=', + border: OutlineInputBorder(), + ), + ), + hint('Browsers block cross-origin feed requests. The feed URL is URL-encoded ' + 'and appended to this prefix. Not needed on Android, iOS or Windows.'), + ], + ], + ), + ); + } +} diff --git a/lib/ui/sources_screen.dart b/lib/ui/sources_screen.dart new file mode 100644 index 0000000..a7927c3 --- /dev/null +++ b/lib/ui/sources_screen.dart @@ -0,0 +1,223 @@ +import 'package:flutter/material.dart'; + +import '../models/news_source.dart'; +import '../state/app_state.dart'; + +class SourcesScreen extends StatelessWidget { + const SourcesScreen({super.key, required this.state, required this.onChanged}); + + final AppState state; + + /// Called when the selection changes so the feed can refresh later. + final VoidCallback onChanged; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: state, + builder: (context, _) { + final theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + title: const Text('Sources'), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(24), + child: Container( + alignment: AlignmentDirectional.centerStart, + padding: const EdgeInsetsDirectional.only(start: 16, bottom: 8), + child: Text( + '${state.enabledSourceIds.length} of ${state.allSources.length} enabled', + style: theme.textTheme.bodySmall, + ), + ), + ), + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () => _addFeed(context), + icon: const Icon(Icons.add), + label: const Text('Add feed'), + ), + body: ListView( + padding: const EdgeInsets.only(bottom: 96), + children: [ + for (final lang in SourceLanguage.values) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), + child: Text('${lang.flag} ${lang.label}', + style: theme.textTheme.titleSmall + ?.copyWith(color: theme.colorScheme.primary)), + ), + for (final source in state.allSources.where((s) => s.language == lang)) + _SourceTile( + source: source, + enabled: state.enabledSourceIds.contains(source.id), + onToggle: (v) { + state.setSourceEnabled(source, v); + onChanged(); + }, + onDelete: source.isCustom + ? () { + state.removeCustomSource(source); + onChanged(); + } + : null, + ), + ], + ], + ), + ); + }, + ); + } + + Future _addFeed(BuildContext context) async { + final added = await showDialog( + context: context, + builder: (_) => _AddFeedDialog(state: state), + ); + if (added == true) { + onChanged(); + if (context.mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar(content: Text('Feed added'))); + } + } + } +} + +class _SourceTile extends StatelessWidget { + const _SourceTile({ + required this.source, + required this.enabled, + required this.onToggle, + this.onDelete, + }); + + final NewsSource source; + final bool enabled; + final ValueChanged onToggle; + final VoidCallback? onDelete; + + @override + Widget build(BuildContext context) { + final host = Uri.tryParse(source.homepage)?.host ?? ''; + final subtitle = [ + if (source.description.isNotEmpty) source.description, + host.replaceFirst('www.', ''), + ].join(' · '); + return SwitchListTile( + value: enabled, + onChanged: onToggle, + title: Text(source.name), + subtitle: Text(subtitle), + secondary: onDelete == null + ? null + : IconButton( + tooltip: 'Remove', + icon: const Icon(Icons.delete_outline), + onPressed: onDelete, + ), + ); + } +} + +class _AddFeedDialog extends StatefulWidget { + const _AddFeedDialog({required this.state}); + + final AppState state; + + @override + State<_AddFeedDialog> createState() => _AddFeedDialogState(); +} + +class _AddFeedDialogState extends State<_AddFeedDialog> { + final _name = TextEditingController(); + final _url = TextEditingController(); + SourceLanguage _language = SourceLanguage.de; + bool _busy = false; + String? _error; + + @override + void dispose() { + _name.dispose(); + _url.dispose(); + super.dispose(); + } + + Future _submit() async { + final name = _name.text.trim(); + final url = _url.text.trim(); + if (name.isEmpty || !url.startsWith('http')) { + setState(() => _error = 'Enter a name and a feed URL starting with http.'); + return; + } + setState(() { + _busy = true; + _error = null; + }); + final error = await widget.state + .addCustomSource(name: name, feedUrl: url, language: _language); + if (!mounted) return; + if (error == null) { + Navigator.pop(context, true); + } else { + setState(() { + _busy = false; + _error = error; + }); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Add RSS / Atom feed'), + content: SizedBox( + width: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: _name, + decoration: const InputDecoration(labelText: 'Name'), + textInputAction: TextInputAction.next, + ), + const SizedBox(height: 12), + TextField( + controller: _url, + decoration: const InputDecoration( + labelText: 'Feed URL', hintText: 'https://example.com/feed.xml'), + keyboardType: TextInputType.url, + onSubmitted: (_) => _submit(), + ), + const SizedBox(height: 16), + SegmentedButton( + segments: [ + for (final l in SourceLanguage.values) + ButtonSegment(value: l, label: Text('${l.flag} ${l.label}')), + ], + selected: {_language}, + onSelectionChanged: (s) => setState(() => _language = s.first), + ), + if (_error != null) ...[ + const SizedBox(height: 12), + Text(_error!, style: TextStyle(color: Theme.of(context).colorScheme.error)), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: _busy ? null : () => Navigator.pop(context, false), + child: const Text('Cancel')), + FilledButton( + onPressed: _busy ? null : _submit, + child: _busy + ? const SizedBox( + width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)) + : const Text('Add'), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..061f98d --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,682 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + sha256: "58c8d17d8d589c4259f773522815115de873c397b977a29993588487d3f9acb4" + url: "https://pub.dev" + source: hosted + version: "4.5.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: e30fba942e3debea7b7e6cdd4f0f59ce89dd403a9865193e3221293b6d1544c6 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: fe638107c5f69119156ada2db5a57734385fac3f64430bd7252a00d3ead2ca4b + url: "https://pub.dev" + source: hosted + version: "10.3.4" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149" + url: "https://pub.dev" + source: hosted + version: "0.3.2" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: caa75bd78f017422912e3a904933113b2428eb79f981ac37acd2dd2a700458ea + url: "https://pub.dev" + source: hosted + version: "3.0.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "64951127f001f546891c86f414972bf49ef2c4c8a4c92c65f5790cc0c9100045" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + html: + dependency: "direct main" + description: + name: html + sha256: "43b67b8f43321ab066817dfac5619596c98bb1b61624e77203bb4351785f9699" + url: "https://pub.dev" + source: hosted + version: "0.15.7" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + url: "https://pub.dev" + source: hosted + version: "2.2.19" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: "direct dev" + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: "direct dev" + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + url: "https://pub.dev" + source: hosted + version: "2.4.13" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + url: "https://pub.dev" + source: hosted + version: "6.3.20" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.dev" + source: hosted + version: "6.3.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: "direct main" + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea + url: "https://pub.dev" + source: hosted + version: "3.1.4" +sdks: + dart: ">=3.8.1 <4.0.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..626ea41 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,123 @@ +name: daily_tech_news +description: "Daily tech news with AI summaries" +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.8.1 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + http: ^1.6.0 + xml: ^6.6.1 + html: ^0.15.7 + shared_preferences: ^2.5.3 + url_launcher: ^6.3.2 + flutter_secure_storage: ^10.3.4 + flutter_inappwebview: ^6.1.5 + path_provider: ^2.1.5 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + flutter_launcher_icons: ^0.14.4 + path_provider_platform_interface: ^2.1.2 + plugin_platform_interface: ^2.1.8 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package + +# App icon. Sources live in assets/icon (SVG + generator script); regenerate the +# platform icons with: dart run flutter_launcher_icons +flutter_launcher_icons: + image_path: "assets/icon/icon.png" + android: "ic_launcher" + adaptive_icon_background: "assets/icon/background.png" + adaptive_icon_foreground: "assets/icon/foreground.png" + adaptive_icon_monochrome: "assets/icon/monochrome.png" + # The artwork already sits inside the safe zone; skip the default 16% inset. + adaptive_icon_foreground_inset: 0 + min_sdk_android: 23 + ios: true + remove_alpha_ios: true + web: + generate: true + image_path: "assets/icon/icon.png" + background_color: "#2231A6" + theme_color: "#3355DD" + windows: + generate: true + image_path: "assets/icon/icon.png" + icon_size: 256 diff --git a/test/article_extractor_test.dart b/test/article_extractor_test.dart new file mode 100644 index 0000000..252a73a --- /dev/null +++ b/test/article_extractor_test.dart @@ -0,0 +1,124 @@ +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/services/article_extractor.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; + +final _base = Uri.parse('https://news.example.com/2026/10/story.html'); + +const _longText = 'This paragraph is long enough to count as real article text for the reader.'; + +void main() { + test('picks the main article, not teaser
s, and drops page furniture', () { + const html = ''' + + +

Short teaser that is not the story at all, just a link.

+
+

Title shown from the feed

+

$_longText one

+

Background

+

$_longText two

+
  • First point with enough characters
  • short
+
We are very happy with the result.
+
flutter pub add webview_flutter
+
A chart
+ + +

$_longText comment

+
+

$_longText footer

+'''; + + final content = ArticleExtractor.parse(html, _base); + final types = content.blocks.map((b) => b.type).toList(); + expect(types, [ + BlockType.paragraph, + BlockType.heading, + BlockType.paragraph, + BlockType.image, + BlockType.listItem, + BlockType.quote, + BlockType.code, + BlockType.image, + BlockType.caption, + ]); + expect(content.blocks[3].text, 'https://news.example.com/img/photo.jpg', + reason: 'lazy-load src is resolved against the page URL'); + expect(content.plainText, isNot(contains('related'))); + expect(content.plainText, isNot(contains('comment'))); + expect(content.plainText, isNot(contains('footer'))); + expect(content.plainText, isNot(contains('Title shown'))); + }); + + test('skips link-only tables of contents and flattens nested lists', () { + const html = ''' +
'''; + final items = ArticleExtractor.parse(html, _base) + .blocks + .where((b) => b.type == BlockType.listItem) + .map((b) => b.text) + .toList(); + expect(items, ['Inner point with a link inside it']); + }); + + test('a redirect to a consent page is reported, not shown as the article', () async { + const consentHtml = '

Besuchen Sie Golem.de wie gewohnt mit Werbung und Tracking, ' + 'indem Sie der Nutzung aller Cookies zustimmen.

'; + final extractor = ArticleExtractor( + client: _RedirectingClient( + Uri.parse('https://www.golem.de/sonstiges/zustimmung/auswahl.html?from=x'), + consentHtml), + ); + const article = Article( + title: 'Story', + link: 'https://www.golem.de/news/story.html', + excerpt: 'Teaser', + source: NewsSource( + id: 'golem', name: 'Golem.de', feedUrl: '', homepage: '', language: SourceLanguage.de), + ); + final content = await extractor.load(article); + expect(content.consentWall, isTrue); + expect(content.plainText, isNot(contains('Cookies'))); + }); + + test('feed content fragments are parsed as a whole', () { + final content = ArticleExtractor.parse( + '

$_longText

$_longText again

', _base, + isFragment: true); + expect(content.blocks, hasLength(2)); + }); +} + +/// Answers every request as if the server had redirected to [finalUrl]. +class _RedirectingClient extends http.BaseClient { + _RedirectingClient(this.finalUrl, this.body); + + final Uri finalUrl; + final String body; + + @override + Future send(http.BaseRequest request) async => + _UrlStreamedResponse(finalUrl, Stream.value(utf8.encode(body)), 200); +} + +class _UrlStreamedResponse extends http.StreamedResponse implements http.BaseResponseWithUrl { + _UrlStreamedResponse(this.url, super.stream, super.statusCode); + + @override + final Uri url; +} diff --git a/test/briefing_history_test.dart b/test/briefing_history_test.dart new file mode 100644 index 0000000..c61796c --- /dev/null +++ b/test/briefing_history_test.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:daily_tech_news/models/briefing.dart'; +import 'package:daily_tech_news/services/storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class _FakePathProvider extends PathProviderPlatform with MockPlatformInterfaceMixin { + _FakePathProvider(this.dir); + + final String dir; + + @override + Future getApplicationSupportPath() async => dir; +} + +DailyBriefing _briefing(DateTime at, String intro) => DailyBriefing( + createdAt: at, + intro: intro, + generatedBy: 'test', + topics: const [ + BriefingTopic(title: 'Topic', summary: 'Summary', sources: [ + ArticleRef(title: 'A', url: 'https://example.com/a', sourceName: 'Example'), + ]), + ], + ); + +void main() { + late Directory dir; + + setUp(() { + dir = Directory.systemTemp.createTempSync('tagesbyte_test'); + PathProviderPlatform.instance = _FakePathProvider(dir.path); + }); + + tearDown(() => dir.deleteSync(recursive: true)); + + test('saves and loads the history, newest first', () async { + SharedPreferences.setMockInitialValues({}); + final storage = await Storage.open(); + final older = _briefing(DateTime(2026, 10, 10, 8), 'Older'); + final newer = _briefing(DateTime(2026, 10, 11, 8), 'Newer'); + + await storage.saveBriefingHistory([older, newer]); + final loaded = await storage.loadBriefingHistory(); + + expect(loaded.map((b) => b.intro), ['Newer', 'Older']); + expect(loaded.first.topics.single.sources.single.url, 'https://example.com/a'); + expect(File('${dir.path}/briefings.json').existsSync(), isTrue); + }); + + test('moves the briefing saved by earlier versions into the history', () async { + final legacy = _briefing(DateTime(2026, 10, 9, 8), 'From before the update'); + SharedPreferences.setMockInitialValues( + {'last_briefing': jsonEncode(legacy.toJson())}); + final storage = await Storage.open(); + + final loaded = await storage.loadBriefingHistory(); + expect(loaded.single.intro, 'From before the update'); + + // Migrated once: the old key is gone and loading again doesn't duplicate. + final prefs = await SharedPreferences.getInstance(); + expect(prefs.getString('last_briefing'), isNull); + expect((await storage.loadBriefingHistory()), hasLength(1)); + }); + + test('a missing or broken file means an empty history', () async { + SharedPreferences.setMockInitialValues({}); + final storage = await Storage.open(); + expect(await storage.loadBriefingHistory(), isEmpty); + + File('${dir.path}/briefings.json').writeAsStringSync('{not json'); + expect(await storage.loadBriefingHistory(), isEmpty); + }); +} diff --git a/test/claude_summarizer_test.dart b/test/claude_summarizer_test.dart new file mode 100644 index 0000000..8fe8055 --- /dev/null +++ b/test/claude_summarizer_test.dart @@ -0,0 +1,119 @@ +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/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( + id: 'verge', + name: 'The Verge', + feedUrl: 'https://example.com/feed', + homepage: 'https://example.com', + language: SourceLanguage.en, +); + +const _articles = [ + Article(title: 'Chip launch', link: 'https://example.com/1', source: _source), + Article(title: 'Big outage', link: 'https://example.com/2', source: _source), +]; + +http.Response _ok(Map body) => + http.Response.bytes(utf8.encode(jsonEncode(body)), 200); + +void main() { + test('briefing request uses structured output and maps article ids', () async { + late Map sent; + late Map headers; + final client = MockClient((request) async { + sent = jsonDecode(request.body) as Map; + headers = request.headers; + return _ok({ + 'stop_reason': 'end_turn', + 'content': [ + {'type': 'thinking', 'thinking': ''}, + { + 'type': 'text', + 'text': jsonEncode({ + 'intro': 'Ein ruhiger Tag.', + 'topics': [ + { + 'title': 'Neuer Chip', + 'summary': 'Details', + 'article_ids': ['a1', 'a99'], + }, + ], + }), + }, + ], + }); + }); + + final summarizer = + ClaudeSummarizer(apiKey: 'sk-test', model: 'claude-opus-5-5', client: client); + final briefing = await summarizer.createBriefing(_articles, SourceLanguage.de); + + expect(headers['x-api-key'], 'sk-test'); + expect(headers['anthropic-version'], '2023-06-01'); + expect(headers['anthropic-beta'], 'server-side-fallback-2026-07-01'); + expect(sent['model'], 'claude-opus-5-5'); + expect(sent['fallbacks'], 'default'); + expect(sent['output_config']['format']['type'], 'json_schema'); + expect(sent['messages'][0]['content'], contains('[a2] (The Verge) Big outage')); + expect(sent['system'], contains('German')); + + expect(briefing.intro, 'Ein ruhiger Tag.'); + expect(briefing.topics.single.sources.single.url, 'https://example.com/1', + reason: 'unknown ids are dropped'); + }); + + test('haiku requests skip the fallback beta', () async { + late http.Request req; + final client = MockClient((request) async { + req = request; + return _ok({ + 'stop_reason': 'end_turn', + 'content': [ + {'type': 'text', 'text': 'TL;DR: ok'}, + ], + }); + }); + final summarizer = + ClaudeSummarizer(apiKey: 'k', model: 'claude-haiku-5-5', client: client); + final text = + await summarizer.summarizeArticle(_articles.first, 'body', SourceLanguage.en); + expect(text, 'TL;DR: ok'); + expect(req.headers.containsKey('anthropic-beta'), isFalse); + expect((jsonDecode(req.body) as Map).containsKey('fallbacks'), isFalse); + }); + + test('refusals and auth errors become readable messages', () async { + final refusal = ClaudeSummarizer( + apiKey: 'k', + model: 'claude-opus-5-5', + client: MockClient((_) async => _ok({'stop_reason': 'refusal', 'content': []})), + ); + await expectLater( + refusal.summarizeArticle(_articles.first, 'x', SourceLanguage.en), + throwsA(isA()), + ); + + final unauthorized = ClaudeSummarizer( + apiKey: 'bad', + model: 'claude-opus-5-5', + client: MockClient((_) async => http.Response( + jsonEncode({ + 'type': 'error', + 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, + }), + 401)), + ); + await expectLater( + unauthorized.summarizeArticle(_articles.first, 'x', SourceLanguage.en), + throwsA(predicate((e) => e.toString().contains('Invalid Claude API key'))), + ); + }); +} diff --git a/test/consent_service_test.dart b/test/consent_service_test.dart new file mode 100644 index 0000000..edca0e7 --- /dev/null +++ b/test/consent_service_test.dart @@ -0,0 +1,101 @@ +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/services/article_extractor.dart'; +import 'package:daily_tech_news/services/consent_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; + +const _articleUrl = 'https://www.golem.de/news/story-2610-1.html'; +final _wallUrl = Uri.parse('https://www.golem.de/sonstiges/zustimmung/auswahl.html' + '?from=${Uri.encodeComponent(_articleUrl)}'); +const _wallHtml = '

Cookies zustimmen

'; +const _articleHtml = '

Die Forscher analysierten aggregierte Daten von über ' + '300 Millionen Arbeitsereignissen in Softwareteams.

'; + +/// Behaves like golem.de: articles redirect to the consent page unless the +/// consent cookie is sent; the accept endpoint sets that cookie. +class _FakeGolem extends http.BaseClient { + final requests = []; + + @override + Future send(http.BaseRequest request) async { + requests.add(request); + final url = request.url; + if (url.path == '/abo/setconsentcookie.php') { + return http.StreamedResponse(Stream.value(utf8.encode('ok')), 200, headers: { + 'set-cookie': 'golem_consent20=simple${url.queryParameters['version']}; ' + 'expires=Mon, 12-Oct-2026 10:43:36 GMT; Max-Age=86400; path=/; domain=golem.de; ' + 'secure,golem_c20date=1791715416; expires=Mon, 12-Oct-2026 10:43:36 GMT; path=/', + }); + } + if (url.path.startsWith('/sonstiges/zustimmung/')) { + return _Page(url, _wallHtml); + } + final consented = (request.headers['Cookie'] ?? '').contains('golem_consent20=simple'); + return consented ? _Page(url, _articleHtml) : _Page(_wallUrl, _wallHtml); + } +} + +class _Page extends http.StreamedResponse implements http.BaseResponseWithUrl { + _Page(this.url, String body) : super(Stream.value(utf8.encode(body)), 200); + + @override + final Uri url; +} + +const _article = Article( + title: 'Story', + link: _articleUrl, + excerpt: 'Teaser', + source: NewsSource( + id: 'golem', name: 'Golem.de', feedUrl: '', homepage: '', language: SourceLanguage.de), +); + +void main() { + test('parseSetCookie splits joined headers without breaking on expires dates', () { + final cookies = ConsentService.parseSetCookie( + 'a=1; expires=Mon, 12-Oct-2026 10:43:36 GMT; path=/,b=two|x; path=/, c=3'); + expect(cookies, {'a': '1', 'b': 'two|x', 'c': '3'}); + }); + + test('accepting Golem uses the version from the consent page', () async { + final golem = _FakeGolem(); + final consent = ConsentService(client: golem); + expect(consent.canAccept(_wallUrl), isTrue); + expect(await consent.accept(_wallUrl, wallHtml: _wallHtml), isTrue); + + final acceptRequest = golem.requests.single.url; + expect(acceptRequest.queryParameters['type'], 'simple'); + expect(acceptRequest.queryParameters['version'], '|260301'); + expect(acceptRequest.queryParameters['referer'], _articleUrl); + expect(consent.cookieHeader(Uri.parse(_articleUrl)), + 'golem_consent20=simple|260301; golem_c20date=1791715416'); + }); + + test('reader view accepts the wall once and then gets the article', () async { + final golem = _FakeGolem(); + final consent = ConsentService(client: golem); + final extractor = ArticleExtractor(client: golem, consent: consent); + + final content = await extractor.load(_article, acceptConsentWalls: true); + expect(content.consentWall, isFalse); + expect(content.plainText, contains('300 Millionen')); + + // The next article reuses the cookie: no second accept request. + golem.requests.clear(); + await extractor.load(_article, acceptConsentWalls: true); + expect(golem.requests.map((r) => r.url.path), ['/news/story-2610-1.html']); + }); + + test('without the setting the wall is reported, not accepted', () async { + final golem = _FakeGolem(); + final extractor = + ArticleExtractor(client: golem, consent: ConsentService(client: golem)); + final content = await extractor.load(_article); + expect(content.consentWall, isTrue); + expect(golem.requests.where((r) => r.url.path.contains('setconsentcookie')), isEmpty); + }); +} diff --git a/test/content_blocking_test.dart b/test/content_blocking_test.dart new file mode 100644 index 0000000..01eee90 --- /dev/null +++ b/test/content_blocking_test.dart @@ -0,0 +1,46 @@ +import 'package:daily_tech_news/services/content_blocking.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + RegExp pattern(int index, {bool ads = true, bool cookies = true}) => RegExp( + ContentBlocking.blockers(ads: ads, cookies: cookies)[index].trigger.urlFilter); + + test('ad pattern blocks ad hosts and their subdomains only', () { + final ads = pattern(0); + expect(ads.hasMatch('https://securepubads.g.doubleclick.net/tag/js/gpt.js'), isTrue); + expect(ads.hasMatch('https://static.criteo.net/js/ld/publishertag.js'), isTrue); + expect(ads.hasMatch('https://connect.facebook.net/en_US/fbevents.js'), isTrue); + expect(ads.hasMatch('https://www.googletagmanager.com/gtm.js?id=1'), isTrue); + + expect(ads.hasMatch('https://www.heise.de/news/doubleclick.net-story.html'), isFalse, + reason: 'the host must match, not the path'); + expect(ads.hasMatch('https://notdoubleclick.net/x'), isFalse); + expect(ads.hasMatch('https://www.facebook.com/heiseonline'), isFalse); + expect(ads.hasMatch('https://www.spiegel.de/netzwelt/'), isFalse); + }); + + test('consent patterns block CMP hosts including first-party cmp. subdomains', () { + final consent = pattern(1); + final firstParty = pattern(2); + expect(consent.hasMatch('https://cdn.privacy-mgmt.com/wrapperMessagingWithoutDetection.js'), + isTrue); + expect(consent.hasMatch('https://cdn.cookielaw.org/scripttemplates/otSDKStub.js'), isTrue); + expect(consent.hasMatch('https://cdn.consentmanager.net/delivery/cmp.min.js'), isTrue); + expect(firstParty.hasMatch('https://cmp.heise.de/unified/wrapperMessagingWithoutDetection.js'), + isTrue); + expect(firstParty.hasMatch('https://www.heise.de/cmp.js'), isFalse); + }); + + test('toggles control which blockers and styles are produced', () { + expect(ContentBlocking.blockers(ads: false, cookies: false), isEmpty); + expect(ContentBlocking.blockers(ads: true, cookies: false), hasLength(1)); + expect(ContentBlocking.cosmeticScript(ads: false, cookies: false), isNull); + + final script = ContentBlocking.cosmeticScript(ads: false, cookies: true)!; + expect(script, contains('#onetrust-consent-sdk')); + expect(script, contains('overflow:auto!important')); + expect(script, isNot(contains('adsbygoogle'))); + // Double-quoted selectors are safe inside the single-quoted JS literal. + expect(script, contains('[id^="sp_message_container"]')); + }); +} diff --git a/test/feed_parser_test.dart b/test/feed_parser_test.dart new file mode 100644 index 0000000..ac06f01 --- /dev/null +++ b/test/feed_parser_test.dart @@ -0,0 +1,98 @@ +import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/services/feed_parser.dart'; +import 'package:flutter_test/flutter_test.dart'; + +const _source = NewsSource( + id: 'test', + name: 'Test', + feedUrl: 'https://example.com/feed', + homepage: 'https://example.com', + language: SourceLanguage.de, +); + +void main() { + test('parses RSS 2.0 items, strips html, finds images and dates', () { + const rss = ''' + + + Test + + <![CDATA[Neue GPU & mehr]]> + https://example.com/a + Sun, 11 Oct 2026 06:18:59 +0200 + Jane Doe + Die neue Grafikkarte ist da und sie ist schneller als je zuvor.

]]>
+ Volltext

]]>
+
+ + HN style + https://example.com/b + Sat, 10 Oct 2026 22:50:10 GMT + Comments]]> + + + No link +
+
'''; + + final items = FeedParser.parse(rss, _source); + expect(items, hasLength(2)); + + final a = items[0]; + expect(a.title, 'Neue GPU & mehr'); + expect(a.link, 'https://example.com/a'); + expect(a.excerpt, startsWith('Die neue Grafikkarte')); + expect(a.contentHtml, contains('

Volltext

')); + expect(a.imageUrl, 'https://example.com/img.jpg'); + expect(a.author, 'Jane Doe'); + expect(a.published, DateTime.utc(2026, 10, 11, 4, 18, 59)); + + final b = items[1]; + expect(b.excerpt, isEmpty, reason: 'bare "Comments" links are dropped'); + expect(b.imageUrl, 'https://example.com/thumb.jpg'); + expect(b.published, DateTime.utc(2026, 10, 10, 22, 50, 10)); + }); + + test('parses Atom entries', () { + const atom = ''' + + heise + + Patchday: Microsoft schließt Lücken + + + 2026-10-11T09:30:00+02:00 + 2026-10-11T10:00:00+02:00 + <p>Microsoft hat zum Patchday zahlreiche Sicherheitslücken geschlossen.</p> + heise Security + +'''; + final items = FeedParser.parse(atom, _source); + expect(items, hasLength(1)); + expect(items.single.link, 'https://example.com/atom-1'); + expect(items.single.excerpt, startsWith('Microsoft hat zum Patchday')); + expect(items.single.published, DateTime.utc(2026, 10, 11, 7, 30)); + expect(items.single.author, 'heise Security'); + }); + + test('ignores tracking pixels when picking the article image', () { + const atom = ''' + + + Softwareentwicklung: KI-Flut + + Teaser text that is long enough. <img src="https://cpx.golem.de/cpx.php?aid=1" width="1" height="1" /> + +'''; + expect(FeedParser.parse(atom, _source).single.imageUrl, isNull); + }); + + test('parseDate handles named zones, short years and garbage', () { + expect(FeedParser.parseDate('Mon, 05 Oct 2026 12:00:00 EST'), + DateTime.utc(2026, 10, 5, 17)); + expect(FeedParser.parseDate('5 Oct 26 12:00 +0000'), DateTime.utc(2026, 10, 5, 12)); + expect(FeedParser.parseDate('not a date'), isNull); + expect(FeedParser.parseDate(''), isNull); + }); +} diff --git a/test/live_feeds_test.dart b/test/live_feeds_test.dart new file mode 100644 index 0000000..d68d734 --- /dev/null +++ b/test/live_feeds_test.dart @@ -0,0 +1,56 @@ +// Hits the real feeds. Run with: LIVE_FEEDS=1 flutter test test/live_feeds_test.dart +import 'dart:io'; + +import 'package:daily_tech_news/data/default_sources.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'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final live = Platform.environment['LIVE_FEEDS'] == '1'; + + test('all default feeds load and parse', () async { + final service = FeedService(); + final failures = []; + for (final source in defaultSources) { + try { + final items = await service.fetchSource(source); + final dated = items.where((a) => a.published != null).length; + final withImage = items.where((a) => a.imageUrl != null).length; + // ignore: avoid_print + print('${source.name.padRight(28)} ${items.length.toString().padLeft(3)} items, ' + '$dated dated, $withImage with image'); + if (items.isEmpty) failures.add('${source.name}: no items'); + } catch (e) { + failures.add('${source.name}: $e'); + } + } + expect(failures, isEmpty); + }, skip: live ? false : 'set LIVE_FEEDS=1 to run', timeout: const Timeout(Duration(minutes: 3))); + + test('reader view extracts the article body', () async { + final service = FeedService(); + final extractor = ArticleExtractor(consent: ConsentService()); + final thin = []; + for (final source in defaultSources) { + final article = (await service.fetchSource(source)).first; + final content = await extractor.load(article, acceptConsentWalls: true); + final counts = {}; + for (final b in content.blocks) { + counts[b.type] = (counts[b.type] ?? 0) + 1; + } + // ignore: avoid_print + print('${source.name.padRight(28)} teaser ${article.excerpt.length.toString().padLeft(4)} ' + '-> ${content.plainText.length.toString().padLeft(5)} chars, ' + '${counts[BlockType.paragraph] ?? 0} paragraphs, ${counts[BlockType.image] ?? 0} images'); + if (content.consentWall) { + thin.add('${source.name} (consent wall)'); + } else if (content.plainText.length < 400) { + thin.add(source.name); + } + } + // ignore: avoid_print + print('Teaser only (paywall / blocked / very short): $thin'); + }, skip: live ? false : 'set LIVE_FEEDS=1 to run', timeout: const Timeout(Duration(minutes: 5))); +} diff --git a/test/paywall_service_test.dart b/test/paywall_service_test.dart new file mode 100644 index 0000000..64142c1 --- /dev/null +++ b/test/paywall_service_test.dart @@ -0,0 +1,85 @@ +import 'package:daily_tech_news/models/article.dart'; +import 'package:daily_tech_news/models/news_source.dart'; +import 'package:daily_tech_news/services/paywall_service.dart'; +import 'package:daily_tech_news/services/storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +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( + id: 'src', + name: 'Source', + feedUrl: 'https://example.com/feed', + homepage: 'https://example.com', + language: SourceLanguage.de, + checkPaywallOnPage: checkPage, + ); + +Article _article(String title, {String? link, bool checkPage = false}) => Article( + title: title, link: link ?? 'https://example.com/$title', source: _source(checkPage: checkPage)); + +Future _storage() async { + SharedPreferences.setMockInitialValues({}); + return Storage.open(); +} + +void main() { + test('recognizes paid and sponsored titles', () async { + final paywall = PaywallService(await _storage(), client: MockClient((_) async => http.Response('', 500))); + expect(paywall.isPaid(_article('heise+ | Essbare Batterie')), isTrue); + expect(paywall.isPaid(_article('(g+) Siri-Ersatz im Eigenbau')), isTrue); + expect(paywall.isPaid(_article('SPIEGEL+ Test')), isTrue); + expect(paywall.isPaid(_article('Apple reduziert iPhone-18-Pro-Bestellungen')), isFalse); + expect(paywall.isPaid(_article('OnePlus 16 vorgestellt')), isFalse); + + expect(paywall.isSponsored(_article('Anzeige: Logitech-Set zum Sparpreis')), isTrue); + expect(paywall.isSponsored(_article('Werbung | Neues Angebot')), isTrue); + expect(paywall.isSponsored(_article('Anzeigenmarkt bricht ein')), isFalse); + }); + + test('checks pages only for flagged sources and remembers results', () async { + final storage = await _storage(); + final requested = []; + final client = MockClient((request) async { + requested.add(request.url.path); + final free = request.url.path.contains('free'); + return http.Response( + '', + 200); + }); + final paywall = PaywallService(storage, client: client); + final paid = _article('Paid story', link: 'https://example.com/paid', checkPage: true); + final free = _article('Free story', link: 'https://example.com/free', checkPage: true); + final unflagged = _article('Other', link: 'https://example.com/other'); + final titled = _article('heise+ | Known', link: 'https://example.com/titled', checkPage: true); + + expect(await paywall.checkPages([paid, free, unflagged, titled]), isTrue); + expect(requested..sort(), ['/free', '/paid'], reason: 'unflagged and title-marked skipped'); + expect(paywall.isPaid(paid), isTrue); + expect(paywall.isPaid(free), isFalse); + + // A fresh service reads the saved results instead of fetching again. + requested.clear(); + final again = PaywallService(storage, client: client); + expect(again.isPaid(paid), isTrue); + expect(await again.checkPages([paid, free]), isFalse); + expect(requested, isEmpty); + }); + + test('network errors are retried on the next refresh', () async { + final storage = await _storage(); + var calls = 0; + final paywall = PaywallService(storage, client: MockClient((_) async { + calls++; + return http.Response('', 503); + })); + final a = _article('Story', link: 'https://example.com/a', checkPage: true); + await paywall.checkPages([a]); + await paywall.checkPages([a]); + expect(calls, 2); + expect(paywall.isPaid(a), isFalse); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..210e293bca62d0a2dd15d59a2363aa7b6cc6a36a GIT binary patch literal 428 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 U+W0K#qY;3fwvkq?#$XdhivQ3- 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 z
    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 U+W0K#qY;3fwvkq?#$XdhivQ3- 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 z
      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 + + + + + + + + + + + + + + + + + + + Tagesbyte + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..d9875c6 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "Tagesbyte", + "short_name": "Tagesbyte", + "start_url": ".", + "display": "standalone", + "background_color": "#2231A6", + "theme_color": "#3355DD", + "description": "Daily tech news with AI summaries", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} \ No newline at end of file diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..0a08c7e --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(daily_tech_news LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "daily_tech_news") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..6dfc5b4 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..eb1d58b --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_inappwebview_windows + flutter_secure_storage_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..152751b --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "de.dailytechnews" "\0" + VALUE "FileDescription", "Tagesbyte" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "daily_tech_news" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 de.dailytechnews. All rights reserved." "\0" + VALUE "OriginalFilename", "daily_tech_news.exe" "\0" + VALUE "ProductName", "Tagesbyte" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..7f26240 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"Tagesbyte", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3647e72a6671789a7687cbf4b8d6591452f9c060 GIT binary patch literal 10692 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 Qb!-~yndpAF<{0^Z016`-cK`qY literal 0 HcmV?d00001 diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_