Skip to content

Grid → detail shared-element transition (needs the item detail to become a pushed destination) #64

Description

@jvsena42

Follow-up to #63, which brought both platforms onto a shared design system and added a motion layer (expressive motion scheme, animated chips and tabs, haptics, SF Symbol effects).

One piece of that motion work was deliberately left out, because it is a structural change rather than an additive one: the grid → detail transition. For a photo-first app this is the single highest-impact animation — the card the user tapped expanding into its detail view, instead of an unrelated sheet sliding up over it.

Why it wasn't done in #63

On both platforms it requires the item detail to become a pushed destination rather than a modal sheet:

  • AndroidModalBottomSheet renders in a separate platform window, and shared-element geometry does not resolve across windows. So no amount of tagging the card and the sheet will work; the detail content has to move into the same composition.
  • iOS.navigationTransition(.zoom(sourceID:in:)) only applies to a NavigationStack push, not to a .sheet.

That is a change to how the detail screen is presented, not a change to how it animates, which is why it wanted its own PR.

What to do

Android

  • Wrap the pager content in SharedTransitionLayout.
  • Tag the card photo in ClothingCard.kt with Modifier.sharedElement(rememberSharedContentState(key = item.id), …).
  • Render ItemDetailContent as an in-composition destination via AnimatedContent instead of ModalBottomSheet. The content is already split out of the sheet wrapper (ItemDetailContent vs ItemDetailSheet), so this is mostly a hosting change.
  • compose.animation is currently only a transitive dependency — add it explicitly to composeApp/build.gradle.kts. SharedTransitionLayout is confirmed present in the resolved org.jetbrains.compose.animation:1.10.0.

iOS

  • .matchedTransitionSource(id:in:) on the card in ClothingCard.swift.
  • .navigationTransition(.zoom(sourceID:in:)) on the pushed detail.
  • Wrap the wardrobe grid in a NavigationStack (iOS 18+, inside the current 18.2 deployment floor — no availability gate needed).

Both

AddItemSheet and CreateOutfitSheet are forms and should stay modal sheets. Only the item detail changes. That leaves the two platforms structurally symmetric: detail = push, create/edit = sheet.

Constraints

  • journeys/outfit-item-grid.xml and journeys/add-first-item.xml both walk the item-detail flow, so they need re-running after this. item_detail_sheet, item_detail_edit and item_detail_delete must keep their identifiers even though the host changes.
  • Back navigation has to keep working from the new destination, including predictive back on Android.
  • Verify on a real device, not just the emulator — this is a motion change and frame pacing matters.

Notes

ExperimentalSharedTransitionApi and the material3 Expressive APIs this builds on are alpha. If the transition misbehaves, the fallback is to leave the detail as a ModalBottomSheet; everything in #63 stands on its own without this.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions