Order status motion

Four motions were explored for an order leaving one Active Orders tab for the next, each covering both steps a partner repeats all day: Start preparing (New → Preparing) and Mark ready (Preparing → Ready). Condense and drop is the one in active work; the other three are paused below for reference, not deleted. The studio now opens on Condense and drop regardless of which one you follow a link from.

Step Business Vessel Open studio →

New → Preparing. The partner taps Start preparing, picks a time, and taps Select Time. Each phone starts right after that tap, with the request in flight and the sheet still up. 207 stays in New. Every motion ends with New 2→1 and Preparing 0→1. The vessel is what Preparing means for the business: a restaurant cooks (a pot), a market collects (a basket).

Preparing → Ready. The partner taps Order is ready. There is no sheet: the button spins while donePrepare runs, then turns into a check. Every motion ends with Preparing 2→1 and Ready 0→1, in Ready's blue. The vessel is a hand-off: a takeaway bag that folds shut and gets a seal, or a shopping bag whose handles come together. A pickup order's button is amber ("Order is ready for pickup"); the rest is identical.

Wrap and dropMediumPaused

Studio →

Formerly Prep drop, Prep flip, and Pack and hand off. The card folds away on its own fold, the header wraps once to the tab's width, a #206 label goes on, and the parcel is tossed into the tab's vessel.

Catch at 2.93 s · settled 4.2 s (bar collapsed)
Catch at 2.80 s · settled 4.05 s (bar collapsed)
Beats
  • 875 ms: a PREPARING stamp lands (Confirm); the timer and button switch.
  • 1180: the body folds away bottom-first on FoldableCardConfig.Fast, cropping the ink with it.
  • 1490: one crease per side over 460 ms while the header gathers to the tab's width.
  • 2100: the #206 label (SegmentTick); the Preparing tab opens its vessel.
  • 2260: the toss, 400 ms on a gravity arc. ~2925: the lid claps on or the basket dips (Confirm).
  • 767 ms: the spinner becomes a check (Confirm). No stamp: the check already said what happened.
  • 1020: the fold. 1330: the wrap. 1933: a blue label (SegmentTick) and the Ready tab opens its bag.
  • 2090: the toss. ~2800: the bag closes (Confirm). Then the counts roll.

Compose: the fold reuses rememberFoldableCardState. The wrap draws three clipped thirds of one rememberGraphicsLayer() recording, the ends on graphicsLayer { rotationY; transformOrigin; cameraDistance }. The vessel is two vector layers so the slip falls between them.

Condense and catchMediumPaused

Studio →

The card winds up, launches, and gathers into a solid #206 token that comes up under the destination tab, squashes against it, rebounds once, and is drawn in. No vessel.

Catch at 2.01 s · settled 3.35 s (bar collapsed)
Catch at 1.90 s · settled 3.25 s (bar collapsed)
Beats
  • Acknowledge (Confirm), a wind-up, then a 720 ms flight on a curve still accelerating at contact.
  • Impact (Confirm): the stretch carries into a squash, the tab is punched up, an impact ring spreads, and the counts roll.

Compose: one overlay Canvas driven by a single Animatable, draw-only. The impact is two springs on FilterTabItem's graphicsLayer.

Wrap and tossMediumPaused

Studio →

Wrap and drop without the vessel: the wrapped parcel lands on the tab itself, squashes, and sinks out of sight as the counts roll.

Catch at 2.66 s · settled 3.55 s (bar collapsed)
Catch at 2.49 s · settled 3.4 s (bar collapsed)
Beats
  • The stamp (New → Preparing) or the check (Preparing → Ready), the fold, one wrap, the label (SegmentTick), a toss.
  • Landing (Confirm): it squashes, rights its tilt, and the tab catches it.

Before anything moves: the filter bar

OrdersFilterBar sits in Modifier.collapsingSection(collapse), driven by TopAppBarState.heightOffset. After a partner scrolls to an order, the bar is usually hidden, so the tab that should receive the order is not on screen. Every motion opens it first: heightOffset springs to 0 (NoBouncy, MediumLow) as the request lands. Today CollapsingTopBarState.expand() snaps, so this needs an animated expand. It costs one layout pass per frame and no recomposition. Untick "Bar collapsed" to see it already open.

Can Compose do the vessel and the flip?

Yes, with what is already on the classpath (Compose Multiplatform 1.11.1). The flip is graphicsLayer { rotationY = angle; cameraDistance = 12f * density } on FilterTabItem from one Animatable; the face is derivedStateOf { abs(angle) < 90f }, so it recomposes only at the two swaps. Compose has no backface culling, and swapping at 90° means it never needs it. The tab bar and the list live in the same Scaffold in ActiveOrdersScreen, so one overlay above both draws the token or parcel and the vessel's two layers. New work: a short-lived departing copy of the card (the refetch removes it from paging at once), the animated expand(), and the four vessel vectors in ui/vectors.

Shared ground rules

What is on screen, from the code

PieceWhere it comes fromWhat it means for motion
The card ActiveOrderCard: a header, then FoldableSections for timer, details, finance, and actions. The sections already fold with rotationX; FoldableCardConfig.Fast is 126 ms per section with a 49 ms stagger.
The tabs OrdersFilterBar in the TopBar: New (amber), Preparing (jade), Ready (blue), each with a count. Counts come from state.statistics. Ready has Waiting pickup / In delivery sub-tabs below it.
Start preparing startPrepare emits OrderStatusChanged; the ViewModel sends OrderLeftList(orderId) when a tab is selected. The event that starts the motion already exists.
Mark ready OrderPrimaryAction.MARK_AS_READY (success, "Order is ready") or MARK_AS_READY_FOR_PICKUP (warning). Both call donePrepare under the mutatingOrderId spinner; bags first when onMarkOrderReady needs them. Same OrderLeftList trigger. The spinner is the motion's lead-in.
Business type BusinessType in AppSettings (Restaurant, Market). Unknown behaves as Restaurant, matching accountAllowsInlineOrderItems. Chooses the vessel: pot or basket for Preparing, takeaway bag or shopping bag for Ready.

Light-theme Haat colors copied from Palette, for this mock only. Times are from the demo's own clock with the bar collapsed. On an Android phone, the haptic beats vibrate through navigator.vibrate after you first tap the page.