Skip to content

Illust detail view with matched-geo lightbox + R-18 setting

jedmund requested to merge claude/illust-detail-view into main

Created by: jedmund

Summary

Tapping a cell in the home feed now pushes an IllustDetailView with an iOS 18 zoom navigation transition, and tapping the detail hero opens a matched-geometry lightbox with pinch-zoom / pan / double-tap-zoom. Adds a "Show R-18 content" toggle in Profile.

  • Detail viewpixillate/Views/IllustDetailView.swift. Full-width grey hero container sized via containerRelativeFrame(.vertical) { h, _ in h - 200 } so the image fits the viewport with a 200pt peek of the metadata below. Metadata block (title, user row with avatar, caption, tag pills) fades in with a 0.25s-delayed animation so it doesn't grow-with-the-zoom mid-transition.
  • Lightboxpixillate/Views/IllustLightboxView.swift + ZoomableImageView.swift. Not a fullScreenCover — it's a SwiftUI .overlay on the detail view so the detail content visibly dims behind it. The hero and the lightbox share a matchedGeometryEffect id so the lightbox scales up from the hero's frame. Inside the lightbox, a UIScrollView-backed ZoomableImageView (layout-aware via a ZoomableScrollView subclass) gives real pinch/pan/inertia/double-tap-zoom. Pull-down at min zoom dismisses back to the detail view — needs alwaysBounceVertical = true so the scroll view wins the gesture contest against the parent zoom-navigation dismissal.
  • R-18 settingpixillate/Storage/AppSettings.swift + toggle in ProfileView. The grid cells blur restricted illusts when off, for safe scrolling / video. The detail view intentionally ignores the setting — if the user tapped through, they've already chosen to look.
  • ModelPixivIllust grew caption, tags, metaSinglePage, and Author.profileImageURLs.medium so the detail view has everything it needs in one round trip. A bestImageURL computed falls back from meta_single_page.original_image_url to image_urls.large.
  • PixivImage — new maxWidth parameter so the detail hero can override the grid's 300pt downsample cap without inflating grid cells.

Known issues (for follow-up)

  • Black-cell-on-next-tap after dismissal. Reproducible on a physical device: tap a cell → detail view opens → dismiss via swipe-back / back button / pull-down → tap another cell → the image either doesn't respond or flashes black until it scrolls off-screen and back. Scrolling the cell out of view and back fully recovers it. Speculated cause is iOS 18 zoom-navigation cleanup leaving residual state on the destination hosting controller. Tried narrowing matchedTransitionSource to the PixivImage and restructuring the detail view to have ScrollView (not ZStack) as its root with .navigationTransition applied to the root — neither fully fixed it. Candidates to try next: swap NavigationLink for Button + NavigationPath, or drop .matchedTransitionSource / .navigationTransition to bisect which side is the trigger.
  • Interactive finger tracking during dismissal. iOS 18's zoom navigation doesn't track the user's finger during pull-down — the reverse animation only runs on commit. A Pinterest-style pull that follows the finger with scale/offset would need a custom DragGesture coordinated with the ScrollView scroll gesture (only active at scroll top) and the matched-geometry reverse. Noted in the file header, deferred for its own pass.

Test plan

  • Tap a cell in the home grid → detail view opens with a zoom transition from the cell's image.
  • Detail hero fills the width with a grey letterbox; metadata peek visible at the bottom of the viewport.
  • Tap the hero → lightbox zooms up from the hero frame via matched geometry; background dims.
  • In the lightbox, pinch to zoom, pan when zoomed, double-tap to toggle zoom.
  • Pull down in the lightbox at min zoom → collapses back to detail view (not all the way to grid).
  • X button dismisses the lightbox with matched-geo reverse.
  • Toggle "Show R-18 content" in Profile → grid cells un-blur / blur; detail view always shows unblurred.
  • Multi-page illusts still render their first page correctly (no crash on missing meta_pages decode).

https://claude.ai/code/session_01RDp8iEpHQ3GgpvDesCBR3s

Merge request reports

Loading