mirror of
https://github.com/pawelorzech/Swoosh.git
synced 2026-03-31 20:15:41 +00:00
224 lines
9.9 KiB
Markdown
224 lines
9.9 KiB
Markdown
# Micro-Animations Design — Swoosh (v2)
|
|
|
|
**Date:** 2026-03-19
|
|
**Status:** Approved
|
|
**Style:** Expressive & playful — bouncy springs, overshoot, lively feedback
|
|
|
|
## Overview
|
|
|
|
Add 32 micro-animations + 8 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.
|
|
|
|
The app now includes: multi-account support, search with filters, swipe-to-dismiss, multi-image galleries, writing statistics dashboard, HTML preview, and hashtag support.
|
|
|
|
## Shared Animation Specs — SwooshMotion
|
|
|
|
Central object in `ui/animation/SwooshMotion.kt`:
|
|
|
|
| Name | Type | Parameters | Use case |
|
|
|------|------|-----------|----------|
|
|
| `Bouncy` | Spring | dampingRatio=0.65, stiffness=400 | FAB entrance, chips, badges |
|
|
| `BouncyQuick` | Spring | dampingRatio=0.7, stiffness=1000 | Press feedback (~150ms settle) |
|
|
| `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 |
|
|
| `StaggerDelayMs` | Long | 50 | List item entrances |
|
|
| `RevealDelayMs` | Long | 80 | Content reveal sequences |
|
|
|
|
### Reduced Motion Support
|
|
|
|
`SwooshMotion` checks `Settings.Global.ANIMATOR_DURATION_SCALE`. When reduced motion is enabled, all springs fall back to `snap()` and all tweens use 0ms duration.
|
|
|
|
### Stagger Pattern
|
|
|
|
Each item uses a `MutableState<Boolean>` toggled via `LaunchedEffect(Unit) { delay(StaggerDelayMs * index); visible = true }`. `AnimatedVisibility` has no built-in delay.
|
|
|
|
### Already-Animated Tracking (LazyColumn)
|
|
|
|
Hoist a `mutableStateMapOf<String, Boolean>()` of already-animated item keys in `FeedScreen`. Check `key !in animatedKeys` before triggering entrance. Only first ~8 visible items on initial load animate. Items from infinite scroll appear instantly.
|
|
|
|
---
|
|
|
|
## Feed Screen — 12 animations
|
|
|
|
### F1. FAB entrance (SPRING/Bouncy)
|
|
Scale 0→1 with overshoot on screen open. Hidden during search mode.
|
|
- `animateFloatAsState` with `Bouncy` spring on scale modifier
|
|
- `LaunchedEffect(Unit)` sets target to 1f
|
|
- FAB already conditionally hidden when `isSearchActive` (line 249)
|
|
|
|
### F2. FAB press (SPRING/BouncyQuick)
|
|
Shrinks to 85%, snaps back. Settles before navigation fires.
|
|
- `Modifier.pointerInput` press → `animateFloatAsState` 0.85f→1f
|
|
- Navigation fires immediately; animation interrupted by transition (fine)
|
|
|
|
### F3. Post cards staggered entrance (SLIDE/Gentle)
|
|
Cascading slide-in from bottom, 50ms delay per item. "Waterfall" effect.
|
|
- `AnimatedVisibility` per item in LazyColumn with `slideInVertically + fadeIn`
|
|
- `mutableStateMapOf<String, Boolean>` tracking (see shared section)
|
|
- Applies to both `SwipeablePostCard` and search-mode `PostCard`
|
|
- Capped at first 8 items, infinite scroll items appear instantly
|
|
|
|
### F4. "Show more" expand (SPRING/Snappy)
|
|
`AnimatedContent(targetState = expanded)` with `fadeIn + expandVertically` / `fadeOut + shrinkVertically`. Replaces discrete text swap in PostCardContent.
|
|
|
|
### F5. Empty states (FADE/Quick + scale)
|
|
All empty states (connection error, filter empty, search no results, normal empty) wrapped in `AnimatedVisibility` with `fadeIn + scaleIn(0.9f)`.
|
|
|
|
### F6. Queue status chip (BOUNCE/Bouncy)
|
|
- UPLOADING: `rememberInfiniteTransition` pulse alpha 0.6→1.0
|
|
- Status change: bounce scale + `animateColorAsState`
|
|
- FAILED: shake via `Animatable` offset oscillation (-4dp→4dp→0dp, 3 cycles)
|
|
|
|
### F7. Snackbar (SLIDE/Snappy)
|
|
Both error snackbar (line 578) and pin confirmation snackbar (line 562) — `AnimatedVisibility` with `slideInVertically` + overshoot.
|
|
|
|
### F8. Search bar (SLIDE/Quick) — NEW
|
|
`SearchTopBar` slides in from top with fade when search activates. Reverse on close.
|
|
- `AnimatedVisibility` wrapping the `if (isSearchActive)` branch (line 157)
|
|
- `slideInVertically(initialOffsetY = { -it }) + fadeIn` / reverse
|
|
|
|
### F9. Filter chips bar (FADE/Quick) — NEW
|
|
`FilterChipsBar` fades in/out when toggling between search and normal mode.
|
|
- Already has `animateColorAsState` for chip selection (line 715)
|
|
- Add `AnimatedVisibility` wrapper for the `if (!isSearchActive)` block (line 263)
|
|
|
|
### F10. Account switcher items (SLIDE/Gentle) — NEW
|
|
Inside `AccountSwitcherBottomSheet` (line 954), account `ListItem`s get staggered slide-in.
|
|
- ModalBottomSheet has built-in slide animation
|
|
- Add stagger pattern to account items inside the sheet
|
|
|
|
### F11. Pinned section header (FADE/Quick) — NEW
|
|
"📌 Pinned" header at line 790 — `AnimatedVisibility` with `fadeIn + slideInVertically` when pinned posts exist.
|
|
|
|
### F12. Account switch overlay (FADE/Quick) — NEW
|
|
"Switching account..." overlay (line 288) — crossfade entrance with scale on the spinner.
|
|
|
|
---
|
|
|
|
## Composer Screen — 9 animations
|
|
|
|
### C1. Image grid preview (SCALE/Bouncy)
|
|
`ImageGridPreview` (line 556) — each image thumbnail scales in with bounce when added. Scale-out on removal.
|
|
- `AnimatedVisibility` per grid item with `scaleIn` using `Bouncy`
|
|
|
|
### C2. Link preview card (SLIDE/Gentle)
|
|
After link loads, card slides up + fades in. Loading: `PulsingPlaceholder` replaces `LinearProgressIndicator` (line 317).
|
|
|
|
### C3. Schedule chip (SPRING/Bouncy)
|
|
Chip at line 365 pops in with `scaleIn` using `Bouncy` when `state.scheduledAt != null`.
|
|
|
|
### C4. Publish button (BOUNCE/BouncyQuick)
|
|
Button at line 421 — bounce on click, loading pulse during submit, `AnimatedContent` icon swap to checkmark on success.
|
|
|
|
### C5. Character counter color (FADE/Quick)
|
|
Three-tier `animateColorAsState`: onSurfaceVariant → tertiary (>280) → error (>500). Matches current logic at line 211.
|
|
|
|
### C6. Action buttons staggered (SCALE/Gentle)
|
|
Publish button (line 421) + Row of [Draft, Schedule] (line 433). Two-step stagger: button first, then row.
|
|
|
|
### C7. Error text (SLIDE/Snappy)
|
|
Error at line 407 — `AnimatedVisibility` with `slideInHorizontally + fadeIn`.
|
|
|
|
### C8. Hashtag chips (SPRING/Bouncy) — NEW
|
|
Extracted tags FlowRow (line 225) — staggered `scaleIn` per chip as tags appear/change.
|
|
|
|
### C9. Edit/Preview crossfade (FADE/Quick) — NEW
|
|
`Crossfade(targetState = state.isPreviewMode)` wrapping the `if (state.isPreviewMode)` branch (line 167). Smooth transition between edit and preview modes.
|
|
|
|
---
|
|
|
|
## Detail Screen — 5 animations
|
|
|
|
### D1. Content reveal (FADE/Gentle)
|
|
Sequential: status row → text → tags → image gallery → link preview → PostStatsSection. 80ms delay each.
|
|
- Stagger pattern with `AnimatedVisibility` per section
|
|
- PostStatsSection (line 307) already has internal `AnimatedVisibility` for expand/collapse
|
|
|
|
### D2. Status badge entrance (SPRING/Bouncy)
|
|
First in reveal sequence (index 0). Scale-in with bounce.
|
|
|
|
### D3. Delete dialog (SCALE/Snappy)
|
|
Replace `AlertDialog` at line 312 with `AnimatedDialog` wrapper.
|
|
|
|
### D4. PostStatsSection (SLIDE/Gentle)
|
|
Last in reveal sequence. Slides up. Internal expand/collapse already animated.
|
|
|
|
### D5. Pin toggle feedback (BOUNCE/Bouncy) — NEW
|
|
Pin icon in TopAppBar (line 78 area) — bouncy scale pulse when toggling featured state. `animateFloatAsState` scale 1→1.2→1.
|
|
|
|
---
|
|
|
|
## Settings Screen — 3 animations
|
|
|
|
### S1. Account card (FADE/Quick) — NEW
|
|
Current account Card (line 78) — fade-in with subtle scale on screen entry.
|
|
|
|
### S2. Disconnect confirmation dialog (SCALE/Snappy)
|
|
**New behavior:** Add confirmation dialog before both "Disconnect Current Account" (line 139) and "Disconnect All Accounts" (line 165). Uses `AnimatedDialog` wrapper.
|
|
|
|
### S3. Theme chip selection (SPRING/Bouncy) — NEW
|
|
In `ThemeModeSelector` (line 184), selected chip gets a brief bouncy scale pulse on selection change.
|
|
|
|
---
|
|
|
|
## Stats Screen — 3 animations (all NEW)
|
|
|
|
### ST1. Stats cards staggered entrance (SCALE/Bouncy)
|
|
Four `StatsCard` composables (lines 68-98) — staggered scale-in with bounce. 50ms delay per card.
|
|
|
|
### ST2. Writing stats reveal (SLIDE/Gentle)
|
|
`OutlinedCard` at line 109 — slide-up with fade after cards complete. Internal `WritingStatRow`s cascade.
|
|
|
|
### ST3. Number count-up (FADE/Quick)
|
|
Stat values animate from 0 to target via `animateIntAsState`. Subtle but adds life to the dashboard.
|
|
|
|
---
|
|
|
|
## Navigation Transitions — 8 routes
|
|
|
|
Each `composable()` receives `enterTransition`, `exitTransition`, `popEnterTransition`, `popExitTransition`.
|
|
|
|
| Route | Enter | Pop Exit |
|
|
|-------|-------|----------|
|
|
| Setup | fadeIn(500ms) | — (inclusive popUpTo) |
|
|
| Feed | fadeIn(300ms) | fadeOut(200ms) |
|
|
| Composer | slideInVertically + fadeIn | slideOutVertically + fadeOut |
|
|
| Detail | slideInHorizontally + fadeIn | slideOutHorizontally + fadeOut |
|
|
| Settings | slideInHorizontally | slideOutHorizontally |
|
|
| Stats | slideInHorizontally | slideOutHorizontally |
|
|
| Preview | slideInVertically + fadeIn | slideOutVertically + fadeOut |
|
|
| AddAccount | slideInVertically + fadeIn | slideOutVertically + fadeOut |
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
```
|
|
ui/
|
|
├── animation/
|
|
│ └── SwooshMotion.kt # Shared specs + reduced motion
|
|
├── components/
|
|
│ ├── AnimatedDialog.kt # Scale-in dialog wrapper
|
|
│ └── PulsingPlaceholder.kt # Pulsing loading placeholder
|
|
├── feed/
|
|
│ └── FeedScreen.kt # F1-F12
|
|
├── composer/
|
|
│ └── ComposerScreen.kt # C1-C9
|
|
├── detail/
|
|
│ └── DetailScreen.kt # D1-D5
|
|
├── settings/
|
|
│ └── SettingsScreen.kt # S1-S3
|
|
├── stats/
|
|
│ └── StatsScreen.kt # ST1-ST3
|
|
└── navigation/
|
|
└── NavGraph.kt # 8 route transitions
|
|
```
|
|
|
|
**New files:** 3 (`SwooshMotion.kt`, `AnimatedDialog.kt`, `PulsingPlaceholder.kt`)
|
|
**Modified files:** 6 (`FeedScreen.kt`, `ComposerScreen.kt`, `DetailScreen.kt`, `SettingsScreen.kt`, `StatsScreen.kt`, `NavGraph.kt`)
|
|
|
|
## Testing Strategy
|
|
|
|
- Existing unit tests pass unchanged (animations don't affect business logic)
|
|
- Manual verification on emulator per animation
|
|
- Test with "Remove animations" accessibility setting for reduced-motion fallback
|