Multi-page illust carousel + grid pill + page-aware lightbox
Created by: jedmund
Summary
Multi-page illust support. Tapping a multi-page post in the detail view now swipes horizontally through all pages, and the grid cell gets a pill in the bottom-right indicating how many images are inside.
-
Grid cell pill — pixillate/Views/MasonryFeed.swift:191-200, 234-242. Second
.overlay(alignment: .bottomTrailing)onPixivImagerenders whenillust.pageCount > 1, using asquare.stack.fillSF Symbol + count in a black capsule at 0.6 opacity. Stacks cleanly above the existing centered R-18 badge because they live in different alignments. 6pt inset from the rounded clip edge so it hugs the corner without touching. -
Unified
PixivIllust.pagescomputed — pixillate/Models/PixivIllust.swift:56-89. One list ofPageentries for both single- and multi-page shapes. Each entry carrieslarge(for the detail hero) andoriginal(for the lightbox). Single-page illusts build one synthetic entry from the top-levelimageURLs+metaSinglePage.originalImageURL; multi-page maps overmetaPages.bestImageURLcollapses topages.first?.original ?? imageURLs.large, so callers that only need the first page's full-res URL still work unchanged.MetaPage.ImageURLsgrew alargefield alongsideoriginal— Pixiv's per-pageimage_urlscarries all the standard sizes, we just weren't decoding them before. -
Detail-view carousel — pixillate/Views/IllustDetailView.swift:146-218. The hero
ZStacknow wraps aTabView(selection: $currentPage)with one entry perillust.pageselement. Single-page illusts still route through the same TabView with one entry so control flow stays uniform. Each tab holds aPixivImage(url: page.large, ...)at the 1200pt downsample cap. -
Custom page indicator — pixillate/Views/IllustDetailView.swift:207-218. The user asked for the indicator to sit below the image, inside the grey hero container —
.tabViewStyle(.page)'s built-in dots overlay the image's bottom edge, so I disable them withindexDisplayMode: .neverand draw my own capsule row. Apple-style elongated pill: current-page capsule stretches to 22pt wide, others stay 6pt dots, animated with.spring(response: 0.3)so the width tracks the swipe. Only renders whenpages.count > 1. -
Page-aware lightbox — pixillate/Views/IllustDetailView.swift:113-127. Previously the lightbox always opened
illust.bestImageURL(effectively page 1). Now it readscurrentPageand loadsillust.pages[currentPage].original, so swiping to page 3 and tapping the hero opens page 3. Clamped withmax(0, min(currentPage, pages.count - 1))to guard stale state.
Known carryovers / limitations
-
Lightbox is still single-page. Swipe to page 3 in the hero, tap to open, tap close → back at page 3 in the hero. But inside the lightbox you can't swipe between pages yet. That would require nesting a paging container around
ZoomableImageViewand dealing with gesture-priority contention between the pager's horizontal drag and the scroll view's pan/zoom. Deferred. -
Memory on long manga illusts. Each page's
largeis cached by Nuke (~1200pt, decoded). A 20-page illust keeps up to 20 decoded bitmaps in the pipeline's 150MB byte-cost limit; eviction should handle this automatically, but if you see jetsam on older devices with heavy manga, flag it and we can drop the cap further. - The black-flash-on-next-tap bug from PR #3's follow-ups still affects the zoom navigation transition. Multi-page illusts inherit the same regression since they use the same push pattern.
Test plan
-
Grid cell renders a black pill with a stacked-squares icon and the page count on multi-page illusts, nothing on single-page. -
R-18 badge (when Show R-18is off) and the page count pill don't overlap visually. -
Tap a multi-page cell → detail view opens with page 1 visible and an elongated-pill indicator showing "1 of N" below the image. -
Swipe horizontally through pages → indicator animates, scrolling the detail view still works, metadata below doesn't jump. -
Tap the hero on page 3 → lightbox opens page 3 at full resolution. -
Close the lightbox → back at page 3 in the hero (not reset to page 1). -
Single-page illusts still render normally — no indicator, no behavior change, lightbox opens the single image. -
iPad: the hero carousel still respects containerRelativeFrame(.vertical) { h - 200 }with the metadata peek below.