| Age | Commit message (Collapse) | Author |
|
The fix before this made site-relative images zoomable by resolving them
against the article's own address. It left the parameter optional, which leaves
the bug one forgetful call site away from coming back -- and coming back
silently, as every image on a site-relative feed quietly refusing to zoom,
which is exactly how it went unnoticed on Ftrain in the first place.
It has no default now. Null is still a legitimate answer, because an article
with no address genuinely cannot resolve anything, but it has to be given on
purpose. A future reader surface that omits it fails to compile rather than
shipping the old behaviour.
Nothing about what the app does changed: the same 157 tests pass, and the six
test call sites that relied on the default now say baseUrl = null, which is
what they always meant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Tapping an image in a Ftrain article did nothing. The zoom affordance was
there, and the rendering path was the ordinary one -- the feed's own HTML, with
reader mode off -- but a tap went nowhere at all.
Tap detection works by wrapping every article image in a link to a scheme
nothing can navigate to, which the WebView then hands to
shouldOverrideUrlLoading. That href has to carry the complete image URL on its
own, so an image whose src was relative got no wrapper and no tap: better an
image that can't be zoomed, the reasoning went, than a link that goes nowhere.
Ftrain's <content:encoded> is entirely site-relative -- every image in it is
src="/art/uploads/..." -- so not one of them was zoomable.
That reasoning had an unexamined premise. The page itself resolves those srcs
perfectly well, against the base URL loadReaderHtml already hands
loadDataWithBaseURL: the article's own link. Resolving them the same way at
HTML-assembly time produces exactly the URL the WebView went and fetched, so
the wrapper can be built after all. Both reader surfaces now pass that base URL
down to the zoom pass. Where there genuinely is no base URL to resolve
against -- nothing else in the app loads reader HTML without one today -- the
old behavior stands and the image stays un-zoomable rather than acquiring a
dead link, and a data: or javascript: src is still refused however it was
reached, since the viewer could never open one.
This is why the report could only have come from a feed with reader mode off.
Reader mode never hits it: Readability rewrites every src to an absolute URL
before the extracted HTML reaches us, which is also why the same images zoom
fine the moment the feed's reader toggle is on.
TDD: site-relative and path-relative resolution, and the whole trip through
wrapReaderHtml, were written first against the unused parameter and went red on
assertion mismatches (3 of 38 in ImageZoomTest), green on the resolution
itself. The data:-src case was written at the same time and passed immediately
-- it pins existing behavior against the new code path rather than having
driven it.
Verified on the emulator against the reported article itself, subscribed live
from ftrain.com/feed.xml with reader mode off: on the previous build, a tap on
the header image left the screen byte-identical; on this one the same tap at
the same point opens the full-screen viewer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Pinching in the reader used to scale the whole WebView like a photo and snap
back to the real font size on release. It now drives WebSettings.textZoom as
the fingers move, so the text itself resizes and reflows during the gesture
with nothing to snap back from; images and margins stay put. The reload at the
new saved size is what returns textZoom to 100%, so the two never overlap --
and loading reader HTML now goes through ReaderWebView.loadReaderHtml, which
does both together, so a future load site can't forget the reset.
Reader view for a tapped link now honors the saved font size and the same
pinch gesture, instead of always rendering at the 18px default.
A zoomed image can be panned again. The viewer computed its pan bounds from
values captured when the dialog first composed -- an empty viewport around a
not-yet-decoded image -- because a pointerInput(Unit) block keeps the lambda it
was created with. Those bounds allowed exactly one offset, 0, so zooming worked
and dragging did nothing. The whole pinch/pan step is now one pure function the
gesture callback calls with values it reads fresh each time.
|
|
Article images could be looked at but never enlarged -- a photo or a
chart in a feed was stuck at whatever size the reader column gave it.
Tapping one now opens a full-screen viewer: pinch to zoom (up to 6x),
double-tap to toggle between fitted and 3x, drag to pan while zoomed,
and dismiss with back, the close button, or a swipe down.
Detecting the tap is the whole difficulty. The reader is a WebView, and
its JavaScript is off deliberately (untrusted article content), so no
in-page script can notice a tap on an <img> and report it out. WebView's
own built-in zoom isn't the answer either: pinching in the reader is
already taken, on purpose, by the reader-font-size gesture, and page
zoom scales the article's whole layout rather than giving an image the
screen. So wrapReaderHtml now wraps every image that isn't already
inside a link in a link to a scheme nothing can navigate to
(feedreader-image:<url>), which WebView hands to shouldOverrideUrlLoading
-- the interception point that already routes ordinary links and YouTube
thumbnails. The new onImageTap callback opens ImageZoomDialog instead of
following it.
Images that already sit inside a link are left alone: ReaderExtractor's
synthesized YouTube thumbnail is exactly that shape, and re-wrapping it
would swallow the tap that plays the video. Those stay zoomable from the
other side -- a link pointing straight at an image file is also treated
as a zoom target, which additionally fixes WordPress's default "link to
media file" markup sending a tap off to LinkReaderScreen to run
Readability over a JPEG. The long-press-link handler strips the scheme
back off, so the actions menu still offers the real image URL.
The viewer is Compose + Coil (AsyncImage), matching what the article
list's thumbnails already use, so a just-scrolled-past image usually
comes straight from Coil's cache; zoom is a graphicsLayer transform, not
a re-decode. Panning is bounded by where the image actually is rather
than by the viewport -- ContentScale.Fit letterboxes anything that isn't
the screen's aspect ratio, and bounding by the viewport would let a wide
photo be dragged up into its own black bar. Coil reports the intrinsic
size once decoded; until then the viewport is the fallback bound.
TDD: the HTML rewriting (linkifyImagesForZoom), the tap-target decision
(zoomableImageTarget), and the zoom/pan math (imageZoomScale,
imageZoomDoubleTapScale, clampImagePanOffset, fittedImageSize) were all
written test-first against stubs, red on assertion mismatches, green on
the real implementations -- 26 new cases in ImageZoomTest, including
three over wrapReaderHtml itself to pin the ordering that keeps the
YouTube thumbnail's link intact. Gesture wiring and the dialog itself
are Compose UI, untestable here for the same reason the rest of ui/ is.
Coverage is unchanged: ui/ is excluded from the Jacoco metric by design.
|