docs: add micro-animations design spec

Define 19 micro-animations + 5 navigation transitions across all
screens with expressive/bouncy style and shared SwooshMotion specs.
This commit is contained in:
Paweł Orzech 2026-03-19 10:28:17 +01:00
parent beef4c4e1b
commit 31b04e549c
No known key found for this signature in database

View file

@ -0,0 +1,178 @@
# Micro-Animations Design — Swoosh
**Date:** 2026-03-19
**Status:** Approved
**Style:** Expressive & playful — bouncy springs, overshoot, lively feedback
## Overview
Add 19 micro-animations + 5 navigation transitions across all screens to make the app feel alive. Create a shared `SwooshMotion` object with predefined animation specs ensuring consistent character throughout the app.
## Shared Animation Specs — SwooshMotion
Central object in a new file `ui/animation/SwooshMotion.kt` providing reusable animation specifications:
| Name | Type | Parameters | Use case |
|------|------|-----------|----------|
| `Bouncy` | Spring | dampingRatio=0.55, stiffness=400 | FAB, buttons, chips |
| `Snappy` | Spring | dampingRatio=0.7, stiffness=800 | Expand/collapse, dialogs |
| `Gentle` | Spring | dampingRatio=0.8, stiffness=300 | Cards, content reveal |
| `Quick` | Tween | 200ms, FastOutSlowInEasing | Fade, color transitions |
| `StaggerDelay` | Offset | 50ms per item | List item entrances |
## Feed Screen — 7 animations
### 1. FAB entrance (SPRING/Bouncy)
When the feed screen opens, the FAB scales from 0 to 1 with `Bouncy` spring. Visible overshoot — the FAB "pops" onto screen.
- Compose API: `animateFloatAsState` with `Bouncy` spring on scale modifier
- Trigger: `LaunchedEffect(Unit)` sets target to 1f
### 2. FAB press (SPRING/Bouncy)
On tap, FAB shrinks to 85% and springs back to 100%.
- Compose API: `Modifier.pointerInput` detecting press → `animateFloatAsState` scale 0.85f → 1f
- Spring spec: `Bouncy`
### 3. Post cards staggered entrance (SLIDE/Gentle)
Cards slide in from bottom with cascading delay — 50ms per item. "Waterfall" effect.
- Compose API: `AnimatedVisibility` per item with `slideInVertically` + `fadeIn`
- Delay: `StaggerDelay * index` (capped at visible items, ~8 max)
- Only on initial load, not on scroll-append
### 4. "Show more" expand (SPRING/Snappy)
Card height animates with spring when text expands/collapses.
- Compose API: `Modifier.animateContentSize(animationSpec = Snappy)`
- Text content enters with `fadeIn`
### 5. Empty state (FADE/Quick + scale)
"No posts yet" icon and text fade in with subtle scale from 0.9 to 1.0.
- Compose API: `AnimatedVisibility` with `fadeIn + scaleIn(initialScale = 0.9f)`
### 6. Queue status chip (BOUNCE/Bouncy)
During upload: chip pulses (infinite alpha animation 0.6→1.0). On status change (success/fail): bounce scale + color crossfade.
- Compose API: `rememberInfiniteTransition` for pulse; `animateColorAsState` + scale bounce on status change
- Color transition: `Quick` tween
### 7. Snackbar error (SLIDE/Snappy)
Slides in from bottom with slight overshoot. Fades out on timeout.
- Compose API: `AnimatedVisibility` with `slideInVertically(initialOffsetY = { it })` + `Snappy` spring
- Exit: `fadeOut` with `Quick` tween
## Composer Screen — 6 animations
### 8. Image preview (SCALE/Bouncy)
After picking an image, the preview scales from 0 with bouncy spring. Close button ("X") rotates in (0°→360° with fade).
- Compose API: `AnimatedVisibility` with `scaleIn` using `Bouncy` spring
- Close button: `animateFloatAsState` on rotation + `fadeIn`
### 9. Link preview card (SLIDE/Gentle)
After link loads, card slides up from below + fades in. While loading: shimmer placeholder.
- Compose API: `AnimatedVisibility` with `slideInVertically + fadeIn` using `Gentle` spring
- Shimmer: infinite `rememberInfiniteTransition` on a translucent gradient offset
### 10. Schedule chip (SPRING/Bouncy)
After picking date, chip pops in with bouncy spring. Clock icon subtly rotates.
- Compose API: `AnimatedVisibility` with `scaleIn` using `Bouncy`
- Icon: `animateFloatAsState` rotation 0→360
### 11. Publish button (BOUNCE/Bouncy)
Subtle bounce on activation. During publishing: loading pulse (alpha animation). On success: checkmark icon scales in.
- Compose API: Scale bounce on click via `animateFloatAsState`; `rememberInfiniteTransition` for pulse; `AnimatedContent` for icon swap with `scaleIn`
### 12. Character counter color (FADE/Quick)
Smooth color crossfade when exceeding 280 characters — neutral → red.
- Compose API: `animateColorAsState` with `Quick` tween
- Trigger: `text.length > 280`
### 13. Action buttons staggered entrance (SCALE/Gentle)
Draft, Schedule, Publish buttons — cascading scale-in with 50ms delay each.
- Compose API: `AnimatedVisibility` per button with `scaleIn` + `StaggerDelay * index`
## Detail Screen — 4 animations
### 14. Content reveal (FADE/Gentle)
Elements appear sequentially: status badge → text → image → metadata. 80ms delay each.
- Compose API: `AnimatedVisibility` per section with `fadeIn + slideInVertically(initialOffsetY = { 20 })`
- Delay: `LaunchedEffect` with `delay(80 * index)`
### 15. Status badge entrance (SPRING/Bouncy)
Badge scales from 0 with bounce — first visible element, draws attention.
- Compose API: `animateFloatAsState` scale with `Bouncy` spring
### 16. Delete confirmation dialog (SCALE/Snappy)
Dialog scales from center (0.8→1.0) with spring + backdrop fades in.
- Compose API: Custom dialog wrapper with `scaleIn(initialScale = 0.8f)` + `fadeIn` backdrop
- Spring spec: `Snappy`
### 17. Metadata section (SLIDE/Gentle)
Bottom metadata slides up — last in the reveal sequence.
- Compose API: `AnimatedVisibility` with `slideInVertically` + `Gentle` spring
## Settings Screen — 2 animations
### 18. "Saved!" feedback (SPRING/Bouncy)
Green "Saved!" text pops in with bounce (scale 0→1). After 2 seconds, fades out.
- Compose API: `AnimatedVisibility` with `scaleIn` using `Bouncy` spring; `LaunchedEffect``delay(2000)` → hide
- Exit: `fadeOut` with `Quick` tween
### 19. Disconnect dialog (FADE+SCALE/Snappy)
Same pattern as delete dialog — scale from center + backdrop fade. "Disconnect" button has subtle red pulse.
- Compose API: Same custom dialog wrapper as #16
- Red pulse: `rememberInfiniteTransition` on alpha of error color
## Navigation Transitions — 5
### Feed → Composer
Slide up from bottom + fade. Conceptually: FAB transforms into full screen.
- `enterTransition = slideInVertically(initialOffsetY = { it }) + fadeIn()`
- `exitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut()`
### Feed → Detail
Slide in from right + fade. Post card expands into full view.
- `enterTransition = slideInHorizontally(initialOffsetX = { it }) + fadeIn()`
- `exitTransition = slideOutHorizontally(targetOffsetX = { it }) + fadeOut()`
### Feed → Settings
Standard slide from right.
- `enterTransition = slideInHorizontally(initialOffsetX = { it })`
- `exitTransition = slideOutHorizontally(targetOffsetX = { it })`
### Back (all screens)
Reverse of the entry animation for each screen.
### Setup → Feed
Crossfade — smooth transition from animated setup background to feed.
- `enterTransition = fadeIn(tween(500))`
- `exitTransition = fadeOut(tween(500))`
## File Structure
```
ui/
├── animation/
│ └── SwooshMotion.kt # Shared animation specs object
├── components/
│ └── AnimatedDialog.kt # Reusable animated dialog wrapper (used by #16, #19)
├── feed/
│ └── FeedScreen.kt # Modified: #1-#7
├── composer/
│ └── ComposerScreen.kt # Modified: #8-#13
├── detail/
│ └── DetailScreen.kt # Modified: #14-#17
├── settings/
│ └── SettingsScreen.kt # Modified: #18-#19
└── navigation/
└── NavGraph.kt # Modified: navigation transitions
```
## New files: 2
- `ui/animation/SwooshMotion.kt`
- `ui/components/AnimatedDialog.kt`
## Modified files: 5
- `FeedScreen.kt`, `ComposerScreen.kt`, `DetailScreen.kt`, `SettingsScreen.kt`, `NavGraph.kt`
## Testing Strategy
- Existing unit tests should pass unchanged (animations don't affect business logic)
- Manual verification: each animation visually correct on emulator
- Compose Preview for individual animated components where feasible