| Age | Commit message (Collapse) | Author |
|
Reverts part of the previous commit: the explicit refresh button and
fetch-on-launch stay, but the user still wants the background 5am alarm so
articles are fresh before the app is opened, not just the periodic hourly
WorkManager job.
|
|
Periodic hourly WorkManager fetches and the 5am exact-alarm morning fetch
both refreshed without any visible trigger, which surprised the user more
than it helped. Refreshing now only ever happens on a fresh app launch
(MainActivity.onCreate) or an explicit tap of the new refresh button on the
Unread/Older screens' top bar (wired to the fetch-status state that was
already being computed there but never rendered). Drops MorningFetchReceiver
entirely along with its exact-alarm permission and boot receiver.
|
|
FeedFetchWorker's periodic 60-minute WorkManager job can (and does)
get silently deferred for hours by Doze on an idle overnight device.
An exact alarm (setExactAndAllowWhileIdle) wakes the device at a fixed
5am to run a fetch, without requesting the much broader "ignore
battery optimizations" exemption -- battery optimization stays on
otherwise, just this one daily wake-up.
Android 12+ requires a one-time user grant for exact alarms (no in-app
permission dialog); MainActivity requests it via Settings if not
already granted, tracked across rotation via savedInstanceState so a
config change doesn't re-trigger the Settings screen. The alarm also
gets re-armed on BOOT_COMPLETED, since AlarmManager alarms don't
survive a device restart on their own.
|
|
The reader used to snapshot the article-id order of its list ONCE at
entry and page a HorizontalPager across that fixed queue. Two real
bugs traced back to one root cause: the snapshot lived in plain
remember state that did NOT survive an orientation change, while the
pager's own page index did -- rotating mid-session left the index
pointing into a freshly re-fetched (and by then shorter, since paging
past an article marks it read and drops it from the Unread query)
list, landing on a different article than the one actually being
read. Back was confusing for the same reason: it always returned to
wherever the ORIGINAL initialArticleId pointed, not wherever the pager
had since scrolled to.
Fixed by not having a separate queue/index at all: each Next/Previous
now replaces this screen's own back-stack entry with a fresh
navigation to the neighboring article's id (MainActivity wires
onNavigateToArticle with the same popUpTo-replace pattern
LinkReaderScreen already uses for its own link chain). "Which article
is showing" is now the nav route argument itself -- Android's own
Saved State mechanism, not custom state that can drift out of sync
with it -- and the back stack is always exactly [list, one reader
entry] no matter how many articles were paged through.
|
|
Ars sequel chrome
- Feed list: folders now collapse (start collapsed, with a per-folder unread
count so collapsing doesn't hide "is there anything new")
- Long-pressing a link inside an article shows an action menu (open in
browser, share, save to Stash, save for later) instead of WebView's
default text-selection popup -- confirmed live end to end, including the
Stash/save-for-later paths creating a real standalone article
- New shared ensureStandaloneArticle() helper (data/ArticleSaving.kt),
factored out of ShareReceiverActivity, now also used by the long-press menu
- YouTube iframe embeds become tap-to-open thumbnails (JS is off in the
reader WebView, so the real player never worked anyway); tapping one goes
straight to the YouTube app/browser instead of our own article-extraction
pipeline
- Fixed back navigation through a chain of links-within-links: each hop was
stacking a new backstack entry, so back stepped through the chain one link
at a time instead of returning straight to the source article. Confirmed
live: A -> link -> B -> link -> C, one back-press now goes straight to A
- Extended the <header> chrome-stripping from last round: also strip
<footer>, an author bio card, and a "Most Read" trending-stories widget --
confirmed live one of the latter showed up as a giant unrelated photo plus
an empty numbered list (its lazy-loaded items never populate in this
hidden, off-screen extraction WebView)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
- ReaderExtractor: the un-hide-before-extracting fix (for soft paywalls
like West Hawaii Today) was unconditional -- it also un-hid normal,
legitimately-collapsed chrome (kottke's footer/membership notice), which
Readability then folded into the result alongside the real article.
Reported live with a screenshot showing the extracted content wrapped in
site footer boilerplate. Now tries the normal (hidden-respecting) pass
first and only falls back to the un-hide pass if that came back thin
(<500 chars), keeping whichever pass actually found more text. Verified
live: the same kottke article now stays clean and stable (no delayed
switch to the footer version).
- New Unread/Older split: the default Unread view is now clamped to the
last 72 hours (RECENT_WINDOW_MS); the rest of the unread backlog moved
to a new "Older" view (new FeedListScreen row, ArticleDao.observeUnread
now takes a cutoff, observeOlderUnread/observeUnreadCount/
observeOlderUnreadCount added). Undated articles are kept in the main
Unread view rather than silently dropped from both. Verified live: 150
recent / 1172 older, summing to the prior combined total.
- "Mark read from here": long-press an article row to mark it and
everything older in that same filtered list as read in one batch
(re-added ArticleDao.markReadBatch, this time as an explicit,
confirmed action -- not the implicit on-scroll version that was
reverted earlier). Verified live: confirmed a 147-article batch: the
tapped article and older were marked read, newer ones above it were not.
- BACKLOG.md: added a YouTube-embeds note (JS is off in the reader
WebView, so embeds currently show broken -- rough shape is a tap-to-play
thumbnail rather than turning JS on for third-party content).
|
|
- ReaderScreen: HorizontalPager's full-surface drag detection was treating
ordinary vertical scrolling inside the embedded WebView (a native View,
which doesn't arbitrate with Compose's gesture system the way a
pure-Compose scrollable child would) as an attempted horizontal page-turn
on anything less than a perfectly vertical swipe -- i.e. most real thumb
scrolling. Tried gating it to edge-zone-only drags first, but that
collides with Android's own edge-swipe-back gesture on gesture-nav
devices (reproduced live: an edge swipe triggered system back, not the
pager). Landed on disabling the Pager's built-in gesture and driving it
from a custom detector that works anywhere on screen but only commits to
a page turn once a drag is clearly horizontal-dominant (>2x the vertical
movement) -- ordinary scrolling, even sloppy/diagonal, is left completely
unconsumed for the WebView. Verified live: a deliberately diagonal
mid-screen scroll now stays on the same article; a decisive horizontal
swipe still changes pages.
- LinkReaderScreen: was missing the bookmark/Stash/reader-toggle/menu
actions entirely -- reported after tapping a link left "all my icons...
gone." Brought to parity with the main article reader: save locally and
save to Stash now lazily create a standalone ArticleEntity (feedId=null,
id = hash of the URL, same scheme ShareReceiverActivity uses) on first
use; reader-mode toggle falls back to the live page (JS on) instead of
always forcing extraction; same login/find-another-source menu. Verified
live: all 5 icons present once extraction finishes, bookmarking a linked
article correctly creates a standalone DB row.
|
|
Deliberately not a paywall-bypass mechanism -- that space (archive.today-style
proxies, "Bypass Paywalls" extensions) is legally grey, adversarial toward
publishers, and constantly breaking. Instead:
- BrowserScreen: a real interactive mini-browser (JS + DOM storage on,
ordinary navigation, no Readability interception) for the two cases where
the user needs to actually USE a page rather than read it. Reached from
a new overflow menu on the article reader:
- "Log in to this site" opens the article's own URL there so the user can
sign into a site they already subscribe to. Needs no extra plumbing:
Android's CookieManager is one process-wide store shared by every WebView
in the app, so a session cookie set here is picked up automatically by
the reader's (JS-off) extraction WebView on the next visit to that
domain -- same as a normal browser remembering you're signed in.
- "Find another source" runs a web search on the article's title in the
same browser, so the user can pick a different, non-paywalled outlet
covering the same story themselves -- plain search/discovery, not
circumvention.
Verified live: search opened a real, interactive Google results page with
working title-bar sync; back navigation (via BackHandler, goBack() when the
WebView has history, else pop the screen) returned cleanly to the article.
|
|
scroll, fix title entities
- ReaderScreen: bookmark icon is now a purely local save (just flips isSaved,
no network) -- Stash archiving is a separate explicit action (new cloud
icon), since it's a heavier networked action the user should trigger on
purpose rather than have bundled into "save." Verified via direct DB
queries and (once I stopped misreading small screenshots) the icon
updates immediately in-session for both actions.
- New ReaderWebView.kt: shared WebView factory for the dark reader palette
and link handling, used by both ReaderScreen and the new LinkReaderScreen.
Links tapped inside an article's WebView now open in a lightweight
in-app reader (extracted via the same Readability pipeline, no DB
persistence) instead of falling through to the system browser --
recursive, so a chain of links stays in-app. Verified live: tapping a
link mid-article navigated to a real extracted page with a working back
stack.
- WebView scroll tuning: overscroll glow and scrollbars off, on both the
article reader and the new link reader -- aimed at the reported "still a
little choppy" scrolling on top of the previous reload-on-recompose fix.
- FeedParser: some feeds (kottke.org among them) double-encode titles --
the XML parser resolves outer XML escaping, but leftover HTML entities
like "“" were showing up literally in the title text. Titles are
now run through a second HTML-entity decode pass. Verified: a kottke
title that previously showed a literal “ now renders the actual
curly quote.
All changes verified live on the emulator against the real 107-feed dataset.
|
|
- Root cause of the reported crashes: observeUnread/observeByFeed/
observeSaved used SELECT *, pulling feedContentHtml/extractedContent
(potentially large per-article blobs) just to render a list row.
With 107 feeds worth of real articles this blows past Android's
CursorWindow/memory limits, scaling with item count -- and since
Unread is now the landing screen, it crashed at startup too. Added
an ArticleListItem projection excluding the heavy content columns;
list queries now only select what a row actually renders.
- FilterRuleEngine now takes a small Titled interface (title/feedId)
instead of the full entity, so it works with both ArticleEntity and
the new lighter projection.
- Reader-mode toggle: a button in the reader to manually switch
between feed-provided content and Readability-extracted full text,
matching Inoreader's full-text-view toggle (previously only an
automatic fallback when feed content was thin).
- Swipe between articles: ReaderScreen now wraps a HorizontalPager over
a snapshot of the current list's article-id order, so swiping moves
to the next/previous article. Snapshotted rather than live, since
marking an article read would otherwise drop it from the live Unread
query mid-swipe and shift every later page underneath the user.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
- Fix OPML import status message reporting the raw pre-dedup count
(144) instead of the actual unique feed count after Room's upsert
collapses duplicates (108) -- cosmetic bug, the DB was already
correct.
- Article thumbnails: extract media:thumbnail/enclosure or the first
<img> in feed content, show via Coil in the article list.
- App now lands on Unread instead of the feed list; a top-bar icon
gets from there to the feed list (forward nav, not back-stack pop,
since Unread is now the root).
- Dark color scheme by default.
- Delete-feed action (with confirmation) on each feed row -- was
entirely missing. Deletes its non-saved articles too, so they don't
linger as orphaned rows in the cross-feed Unread view; saved articles
are kept regardless.
- Room migration 1->2 for the new imageUrl column (real migration, not
destructive fallback, since there's now real on-device subscription
data from testing).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|