Tier 3 UIKit rewrite: grids, navigation, hero transitions
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) viaUIHostingConfiguration. - 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 withFloatingHeroCoordinator,FloatingHeroOverlay,MainTabView,HomeView,ProfileView,TagFeedView,SearchView,IllustDetailOverlay, and their support files.IllustCellBody/PixivImage/IllustFeedViewModel/MasonryDistribution/ProfileHeaderView/SettingsSheetare preserved and reused by the UIKit cells. TheuikitHomeEnabledfeature flag is removed; UIKit is the only path.
Commits
-
963de32ExtractIllustCellBody— pure visual for shared SwiftUI + UIKit use. -
17f9358Phase 1: UIKit Home grid + detail stub behinduikitHomeEnabledflag. -
97d1d75Port hero + metadata cells to UIKit for detail VC. -
952323dAdd UIKit lightbox VC presented from detail hero tap. -
757efeePlumb bookmark mutation through feed view model. -
ab92601Replace detail stub with compositional-layout detail VC. -
e165f0fLet feed VM swap its first-page endpoint in place (for profile tab swaps). -
61ae934Bring Phase-3 UIKit tabs live: Profile, Tag, Search. -
5ddd92fPhase 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 → TagFeedViewControllerpushes. 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 → TagFeedViewControllerpushes with the resolved tag. Empty state renders viaUIContentUnavailableConfiguration.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. isPremiumthreads fromContentViewthrough every VC push. -
Regression — dead-code grep — FloatingHeroCoordinator,FloatingHeroOverlay,MainTabView,overlayPath,overlayScrollAnchors,uikitHomeEnabled,PlaceholderViewControllerreturn zero matches in the source tree.