summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/data
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-22 17:56:50 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-22 17:56:50 +0000
commit9da3fbcdef71e90d52045531c7c18faff02f2ff6 (patch)
treee4c9d3444ca5fa4d305b6868d0938b0b44c146c2 /app/src/main/java/org/terst/feedreader/data
parent6d9c200dbd85a3be177d6bb7cac33fba5662ac95 (diff)
Smooth scrolling, dark reader, mark-read-on-scroll, feed names, per-feed reader-mode opt-out
- ReaderScreen: AndroidView's update() was reloading the WebView's HTML on every recomposition (e.g. isSaving flipping mid-read), resetting scroll position -- this is what made scrolling feel unsmooth. Now only reloads when the content string actually changes. - ReaderScreen: reader content now uses a hardcoded dark palette instead of browser-default white, matching the app's always-dark theme. - ArticleListScreen: mark-as-read-on-scroll -- once an article has scrolled fully above the viewport it's marked read, the standard feed-reader behavior, via a new batched ArticleDao.markReadBatch(). Verified against the on-device DB (scrolled-past articles flipped to isRead=1, the still-visible one stayed unread). - ArticleListItem/queries: LEFT JOIN feeds for a feedTitle column, shown per row in the Unread/Saved aggregated views and as a reader subtitle; the per-feed article list's top bar now shows the real feed title instead of a generic "Articles". - FeedEntity.readerModeDisabled (schema v3, migration 2->3): per-feed opt-out of the Readability fallback/toggle, toggled from a new icon in FeedListScreen. Added for kottke.org specifically -- confirmed its thin linkblog posts were making Readability extract the page footer instead of the actual post; disabling reader mode for that feed now shows its own (short but correct) content instead. All verified live on the emulator against the real 107-feed dataset.
Diffstat (limited to 'app/src/main/java/org/terst/feedreader/data')
-rw-r--r--app/src/main/java/org/terst/feedreader/data/AppDatabase.kt11
-rw-r--r--app/src/main/java/org/terst/feedreader/data/Daos.kt20
-rw-r--r--app/src/main/java/org/terst/feedreader/data/Entities.kt3
3 files changed, 26 insertions, 8 deletions
diff --git a/app/src/main/java/org/terst/feedreader/data/AppDatabase.kt b/app/src/main/java/org/terst/feedreader/data/AppDatabase.kt
index e2f6c1a..4cf0ecd 100644
--- a/app/src/main/java/org/terst/feedreader/data/AppDatabase.kt
+++ b/app/src/main/java/org/terst/feedreader/data/AppDatabase.kt
@@ -9,7 +9,7 @@ import androidx.room.migration.Migration
@Database(
entities = [FeedEntity::class, ArticleEntity::class, FilterRuleEntity::class],
- version = 2,
+ version = 3,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
@@ -31,12 +31,19 @@ abstract class AppDatabase : RoomDatabase() {
}
}
+ // v3: added FeedEntity.readerModeDisabled (per-feed reader-mode opt-out).
+ private val MIGRATION_2_3 = object : Migration(2, 3) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE feeds ADD COLUMN readerModeDisabled INTEGER NOT NULL DEFAULT 0")
+ }
+ }
+
fun get(context: Context): AppDatabase = instance ?: synchronized(this) {
instance ?: Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"feedreader.db"
- ).addMigrations(MIGRATION_1_2).build().also { instance = it }
+ ).addMigrations(MIGRATION_1_2, MIGRATION_2_3).build().also { instance = it }
}
}
}
diff --git a/app/src/main/java/org/terst/feedreader/data/Daos.kt b/app/src/main/java/org/terst/feedreader/data/Daos.kt
index f80cce3..8fe2ec2 100644
--- a/app/src/main/java/org/terst/feedreader/data/Daos.kt
+++ b/app/src/main/java/org/terst/feedreader/data/Daos.kt
@@ -48,25 +48,30 @@ data class ArticleListItem(
val isRead: Boolean,
val isSaved: Boolean,
val stashArtifactId: String?,
- val createdAt: Long
+ val createdAt: Long,
+ val feedTitle: String? // via LEFT JOIN feeds -- null for standalone share-sheet saves with no feedId
) : Titled
private const val ARTICLE_LIST_COLUMNS =
- "id, feedId, title, link, author, publishedAt, imageUrl, isRead, isSaved, stashArtifactId, createdAt"
+ "articles.id, articles.feedId, articles.title, articles.link, articles.author, articles.publishedAt, " +
+ "articles.imageUrl, articles.isRead, articles.isSaved, articles.stashArtifactId, articles.createdAt, " +
+ "feeds.title AS feedTitle"
+
+private const val ARTICLE_LIST_FROM = "articles LEFT JOIN feeds ON articles.feedId = feeds.id"
@Dao
interface ArticleDao {
// Unread-first, newest-first -- the default "what's new" view across every feed.
- @Query("SELECT $ARTICLE_LIST_COLUMNS FROM articles WHERE isRead = 0 ORDER BY publishedAt DESC")
+ @Query("SELECT $ARTICLE_LIST_COLUMNS FROM $ARTICLE_LIST_FROM WHERE articles.isRead = 0 ORDER BY articles.publishedAt DESC")
fun observeUnread(): Flow<List<ArticleListItem>>
@Query("SELECT feedId, COUNT(*) as count FROM articles WHERE isRead = 0 AND feedId IS NOT NULL GROUP BY feedId")
fun observeUnreadCounts(): Flow<List<FeedUnreadCount>>
- @Query("SELECT $ARTICLE_LIST_COLUMNS FROM articles WHERE feedId = :feedId ORDER BY publishedAt DESC")
+ @Query("SELECT $ARTICLE_LIST_COLUMNS FROM $ARTICLE_LIST_FROM WHERE articles.feedId = :feedId ORDER BY articles.publishedAt DESC")
fun observeByFeed(feedId: String): Flow<List<ArticleListItem>>
- @Query("SELECT $ARTICLE_LIST_COLUMNS FROM articles WHERE isSaved = 1 ORDER BY createdAt DESC")
+ @Query("SELECT $ARTICLE_LIST_COLUMNS FROM $ARTICLE_LIST_FROM WHERE articles.isSaved = 1 ORDER BY articles.createdAt DESC")
fun observeSaved(): Flow<List<ArticleListItem>>
@Query("SELECT * FROM articles WHERE id = :id")
@@ -84,6 +89,11 @@ interface ArticleDao {
@Query("UPDATE articles SET isRead = :read WHERE id = :id")
suspend fun setRead(id: String, read: Boolean)
+ // Used for mark-as-read-on-scroll in the article list -- batched per scroll-settle
+ // rather than one query per row.
+ @Query("UPDATE articles SET isRead = 1 WHERE id IN (:ids)")
+ suspend fun markReadBatch(ids: List<String>)
+
@Query("UPDATE articles SET isRead = 1 WHERE feedId = :feedId")
suspend fun markAllReadForFeed(feedId: String)
diff --git a/app/src/main/java/org/terst/feedreader/data/Entities.kt b/app/src/main/java/org/terst/feedreader/data/Entities.kt
index 29d4fe4..f943f78 100644
--- a/app/src/main/java/org/terst/feedreader/data/Entities.kt
+++ b/app/src/main/java/org/terst/feedreader/data/Entities.kt
@@ -12,7 +12,8 @@ data class FeedEntity(
val lastFetchedAt: Long? = null,
val etag: String? = null, // conditional-GET caching (If-None-Match) to avoid re-downloading unchanged feeds
val lastModified: String? = null, // conditional-GET caching (If-Modified-Since)
- val lastError: String? = null // set when a fetch fails; cleared on the next successful fetch
+ val lastError: String? = null, // set when a fetch fails; cleared on the next successful fetch
+ val readerModeDisabled: Boolean = false // per-feed opt-out: never auto/manually switch to Readability for this feed's articles (e.g. a feed whose own formatting is better than what extraction produces)
)
@Entity(tableName = "articles")