| name | m3e-swipe-standards |
| description | Smooth, sticky, haptic swipe-to-act on list rows for Hora-family Android apps — the gesture feel AND the wiring rule that stops a kept (non-removed) row from sticking off-screen. |
M3E swipe-action standards
For apps that DO use a list swipe-to-act, this is the family's one shared feel: a sticky, springy,
haptic right-swipe that reveals an accent panel + check icon, then settles back into place. The row
is never removed — the swipe just toggles state.
Reference impl: Varisankya util/SwipeHelpers.kt (SwipeActionCallback) + SubscriptionActionHelper
(swipe-to-mark-paid). Copy SwipeActionCallback per app — it references the app's own R/colors.
Keep the behaviour below identical.
Not every app uses this. A row-swipe action is optional. Pathivu deliberately has no row
swipe — it marks habits done via an on-row check button and reorders via long-press drag (the
swipe-to-mark-done gesture was tried and removed for being error-prone). Adopt this skill only when
a swipe-to-act genuinely earns its place; a tappable control is often the calmer choice.
The gesture (SwipeActionCallback : ItemTouchHelper.SimpleCallback)
- Right-swipe only for the action; paint the reveal (accent background + check) in
onChildDraw.
- Sticky damping for the "sticks, then tears past" feel: within a
STICK_ZONE (~0.15 of width)
the row follows the finger at STICK_FOLLOW_RATIO (~0.45); past it, 1:1. Apply the damped dX to
BOTH the row translation and the painted reveal width.
- Escalating haptics (all via the gated preference helper — see
m3e-haptic-standards): a light
tick on first nudge (~0.05 of width), a stronger tick at the sticky-zone exit (~0.15).
- Keep ItemTouchHelper's natural recover (standard
getSwipeThreshold ≈ 0.4). That recover IS the
smooth, bouncy spring-back — do NOT replace it with an instant translationX = 0 snap or a
"no-commit" (getSwipeThreshold/getSwipeEscapeVelocity maxed-out) hack: those stop the stuck but
feel stuttery and cheap.
The wiring rule — THIS is what prevents the stuck (get it right)
The row isn't removed, so it must be restored AND the data write must not fight the recover:
- In
onSwipeRight, call adapter.notifyItemChanged(position) synchronously (not posted) —
this springs the row smoothly back.
- Fire the data write only after the spring-back animation settles — NEVER an immediate async
write in
onSwipeRight. Its updateData/DiffUtil rebind otherwise lands mid-recover, cancels it,
and leaves the row stuck off-screen at the edge.
- Deferring behind a confirmation sheet (Varisankya) satisfies this — the write fires long after.
- With no confirmation, defer event-based, not on a fixed timer (a timer races on slow
devices):
recyclerView.postOnAnimation { recyclerView.itemAnimator?.isRunning { <write> } }.
Checklist