Skip to content

Tier 3 UIKit rewrite: grids, navigation, hero transitions

jedmund requested to merge jedmund/detail-durable-lift-state into main

Created by: jedmund

Summary

  • Replaces the SwiftUI masonry + detail + overlay stack with a UIKit-rooted tab controller, per-tab UINavigationController, compositional-layout detail VC, and interactive pan-to-dismiss hero transition. SwiftUI lives at the leaves (cells, metadata rows, profile header) via UIHostingConfiguration.
  • Three tabs land live in this PR: HomeFeedViewController (following feed), SearchViewController (tag typeahead → TagFeedViewController), ProfileViewController (works + bookmarks segmented). Detail pushes to profiles, tag feeds, and nested details at arbitrary depth through UIKit's native navigation stack — no more durable overlay state machine.
  • Shipped as a scope swap: the pre-rewrite durable-overlay pillars (overlayPath, sourceFrame, reprime, overlayScrollAnchors) get deleted along with FloatingHeroCoordinator, FloatingHeroOverlay, MainTabView, HomeView, ProfileView, TagFeedView, SearchView, IllustDetailOverlay, and their support files. IllustCellBody / PixivImage / IllustFeedViewModel / MasonryDistribution / ProfileHeaderView / SettingsSheet are preserved and reused by the UIKit cells. The uikitHomeEnabled feature flag is removed; UIKit is the only path.

Commits

  1. 963de32 Extract IllustCellBody — pure visual for shared SwiftUI + UIKit use.
  2. 17f9358 Phase 1: UIKit Home grid + detail stub behind uikitHomeEnabled flag.
  3. 97d1d75 Port hero + metadata cells to UIKit for detail VC.
  4. 952323d Add UIKit lightbox VC presented from detail hero tap.
  5. 757efee Plumb bookmark mutation through feed view model.
  6. ab92601 Replace detail stub with compositional-layout detail VC.
  7. e165f0f Let feed VM swap its first-page endpoint in place (for profile tab swaps).
  8. 61ae934 Bring Phase-3 UIKit tabs live: Profile, Tag, Search.
  9. 5ddd92f Phase 4: flip to UIKit default, delete SwiftUI detail/overlay stack.

Test plan

  • Cold launch — signed-in user lands in HomeFeedViewController. Masonry grid fills with 2/3/4/5 columns at 500/1100/1300pt width breakpoints. Pull-to-refresh works; pagination kicks in near the tail.
  • Home → detail → back — tap any cell. Hero animates from the grid cell frame into the detail VC's hero cell. Swipe down from the hero to dismiss; spring returns to the originating cell. Pan partway and release above threshold to commit, below to cancel.
  • Multi-page illust — tap a multi-page work. Pager cells exist and swipe cleanly; tapping the hero opens the UIKit lightbox. Dismiss lightbox, dismiss detail.
  • Bookmark mutation — bookmark toggle on a detail VC reflects in the grid cell when you dismiss back (feed VM receives the mutation).
  • Profile push — from detail, tap the user row → ProfileViewController(isCurrentUser: false) pushes. Segmented control swaps works bookmarks (feed VM swaps first-page endpoint in place). Tap a cell → nested detail pushes.
  • Tag push — from detail, tap a tag pill → TagFeedViewController pushes. Grid paginates. Tap a cell → detail pushes.
  • Arbitrary-depth nav — Home → detail → profile → detail → profile → detail. Every push is a native pushViewController; every pop (edge-swipe or pan-to-dismiss on details) returns to the prior VC with its scroll position intact.
  • Search tab — type 2+ chars → tag suggestions appear after 250ms debounce. Tap a suggestion or submit free text → TagFeedViewController pushes with the resolved tag. Empty state renders via UIContentUnavailableConfiguration.empty() before typing.
  • Profile tab — current user's profile renders with header, works/bookmarks segmented control works, sign-out from settings triggers onLogout.
  • Premium gating — for a non-premium user, tag feed uses preview endpoint; for premium, uses popular. isPremium threads from ContentView through every VC push.
  • Regression — dead-code grepFloatingHeroCoordinator, FloatingHeroOverlay, MainTabView, overlayPath, overlayScrollAnchors, uikitHomeEnabled, PlaceholderViewController return zero matches in the source tree.

🤖 Generated with Claude Code

Merge request reports

Loading