From d8ce4b3c0f383e844b125d2d3abd618715bb3c89 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 22 Jul 2026 08:08:58 +0000 Subject: Initial commit: Feeds, an Android RSS reader Feed subscriptions (RSS 2.0 + Atom, hand-rolled parser), OPML import/export, WorkManager periodic fetch with conditional GET, Room-backed local storage (feeds/articles/filter rules), keyword mute/highlight filtering. Reader mode: on-device extraction via Mozilla's Readability.js (vendored from github.com/mozilla/readability, pinned to release 0.6.0) run in a hidden WebView, for feed articles whose provided content is too thin to be the real article body. Save: explicit action (from a feed article, or the Android share-sheet target for any URL) archives to Stash via POST /artifacts?sync=true, while a local on-device copy (Readability-extracted) is always kept so saved articles read offline regardless of Stash's reachability. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL --- .../java/org/terst/feedreader/ui/MainActivity.kt | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 app/src/main/java/org/terst/feedreader/ui/MainActivity.kt (limited to 'app/src/main/java/org/terst/feedreader/ui/MainActivity.kt') diff --git a/app/src/main/java/org/terst/feedreader/ui/MainActivity.kt b/app/src/main/java/org/terst/feedreader/ui/MainActivity.kt new file mode 100644 index 0000000..345bede --- /dev/null +++ b/app/src/main/java/org/terst/feedreader/ui/MainActivity.kt @@ -0,0 +1,79 @@ +package org.terst.feedreader.ui + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import org.terst.feedreader.work.FeedFetchWorker +import java.net.URLDecoder +import java.net.URLEncoder + +const val UNREAD_FEED_ID = "__unread__" +const val SAVED_FEED_ID = "__saved__" + +fun encodeNavArg(value: String): String = URLEncoder.encode(value, "UTF-8") +fun decodeNavArg(value: String): String = URLDecoder.decode(value, "UTF-8") + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + FeedFetchWorker.schedulePeriodic(applicationContext) + + setContent { + MaterialTheme { + Surface(modifier = Modifier.fillMaxSize()) { + FeedReaderApp() + } + } + } + } +} + +@Composable +fun FeedReaderApp() { + val navController = rememberNavController() + NavHost(navController = navController, startDestination = "feeds") { + composable("feeds") { + FeedListScreen( + onOpenFeed = { feedId -> navController.navigate("articles/${encodeNavArg(feedId)}") }, + onOpenUnread = { navController.navigate("articles/${encodeNavArg(UNREAD_FEED_ID)}") }, + onOpenSaved = { navController.navigate("articles/${encodeNavArg(SAVED_FEED_ID)}") }, + onAddFeed = { navController.navigate("addFeed") }, + onSettings = { navController.navigate("settings") } + ) + } + composable( + "articles/{feedId}", + arguments = listOf(navArgument("feedId") { type = NavType.StringType }) + ) { backStackEntry -> + val feedId = decodeNavArg(backStackEntry.arguments?.getString("feedId") ?: UNREAD_FEED_ID) + ArticleListScreen( + feedId = feedId, + onOpenArticle = { articleId -> navController.navigate("reader/${encodeNavArg(articleId)}") }, + onBack = { navController.popBackStack() } + ) + } + composable( + "reader/{articleId}", + arguments = listOf(navArgument("articleId") { type = NavType.StringType }) + ) { backStackEntry -> + val articleId = decodeNavArg(backStackEntry.arguments?.getString("articleId") ?: "") + ReaderScreen(articleId = articleId, onBack = { navController.popBackStack() }) + } + composable("addFeed") { + AddFeedScreen(onDone = { navController.popBackStack() }) + } + composable("settings") { + SettingsScreen(onBack = { navController.popBackStack() }) + } + } +} -- cgit v1.2.3