-
released this
2026-05-15 14:30:30 +02:00 | 7 commits to main since this releaseGPS2Audio v2.0.2 — Drag-and-Drop Waypoint Reorder
Artifacts
- Signed APK:
/home/user/workspace/GPS2Audio_v2_0_2_drag_reorder_signed.apk - Source ZIP (no secrets):
/home/user/workspace/GPS2Audio_v2_0_2_drag_reorder_source.zip - This handoff:
/home/user/workspace/GPS2Audio_v2_0_2_drag_reorder_handoff.md
Version / signing
applicationId:de.waypointaudioversionName:2.0.2versionCode:30(was 29 in v2.0.1)- Signed with the existing release keystore
(/home/user/workspace/GPS2Audio_release_keystore.jks,
aliasgps2audio_release). - Signing certificate (verified with
apksigner verify --print-certs):- DN:
CN=GPS2Audio, OU=NesoHub, O=GPS2Audio, L=Germany, C=DE - SHA-256:
7B:0E:A4:D5:52:86:05:17:3D:29:9D:7B:26:8E:7A:B8:76:8D:D5:04:EC:15:B6:6A:52:D4:7B:42:57:B3:BB:46 - SHA-1:
EB:92:2A:83:39:09:ED:6B:2D:CB:62:52:F0:CF:42:5F:26:DF:25:AD
→ identical to v2.0.0 / v2.0.1, so installs upgrade in place from any
earlierde.waypointaudiobuild.
- DN:
aapt dump badgingconfirmed:
package: name='de.waypointaudio' versionCode='30' versionName='2.0.2'.gradle :app:assembleRelease→ BUILD SUCCESSFUL (only pre-existing
deprecation warnings:launchWhenStarted, OSMDroid setters,
Modifier.menuAnchor()— unchanged from v2.0.1).
Headline change — direct drag-and-drop reordering
Replaces the per-card
▲▼IconButton column from v2.0.1 with a true
drag-and-drop gesture on every waypoint card. The user asked to switch
directly to drag-and-drop control; the arrow column was removed
rather than augmented.Drag interaction (user-facing)
- Every Waypoint-Track card now shows a left-edge drag handle
(Icons.Filled.DragIndicator). German content description:
Ziehgriff zum Verschieben(R.string.drag_handle_cd). - Two ways to start a reorder:
- Press the drag handle and drag (immediate, no long-press).
The handle has its ownpointerInputblock — it doesn't compete
with the outerverticalScroll, so a press on the handle never
starts a page scroll. - Long-press anywhere on the card and drag. This uses
detectDragGesturesAfterLongPresson the whole card and exists as
an accessibility / touch-target alternative; the long-press
threshold protects normal vertical scrolling.
- Press the drag handle and drag (immediate, no long-press).
- While dragging:
- The dragged card is lifted via
graphicsLayer.translationY,
elevated to 8.dp, recoloured toprimaryContainer @ 0.65f, and
pulled above its siblings withzIndex(1f). - The other cards in the affected range shift by their own measured
height (+/- pixels viagraphicsLayer.translationY) so the gap
appears at the prospective drop position. Reordering target is
computed from cumulative drag offset and the per-item heights
measured viaonGloballyPositioned, with a half-height threshold
per neighbour (seeDragReorderState.computeTargetIndex).
- The dragged card is lifted via
- On finger up,
WaypointViewModel.moveWaypoint(from, to)is called
once. That goes through the existing repository.saveAll path, so the
final order is persisted exactly the same way the old arrow buttons
persisted theirs. Cancelling the drag (e.g. system back / system
cancel) snaps the items back without calling moveWaypoint. - Reorder is disabled while the GPS service is running OR a track
recording is active (reorderBlocked = serviceRunning || isRecording):- Both pointerInput blocks short-circuit to
Modifierso no drag can
start. - The DragIndicator icon is shown at alpha 0.20 to read as disabled.
- The pre-existing concise hint
Sortieren während GPS/Aufzeichnung deaktiviert
(R.string.reorder_blocked_hint_short) continues to appear at the
right of theWegpunkt-Tracksheading when more than one waypoint
exists.
- Both pointerInput blocks short-circuit to
Implementation summary
All code touch points:
app/src/main/kotlin/de/waypointaudio/ui/WaypointListScreen.kt- Added imports:
detectDragGestures,
detectDragGesturesAfterLongPress,graphicsLayer,pointerInput,
onGloballyPositioned,semantics/contentDescription,zIndex,
kotlin.math.abs,kotlin.math.roundToInt. - Replaced the
WaypointCardarrow column with a drag handle Box
containingIcons.Filled.DragIndicator. - Wired both gesture detectors:
detectDragGestureson the handle (immediate, no long-press).detectDragGesturesAfterLongPresson the card surface
(accessibility alternative).
- Added a per-list
DragReorderState(held in aremember { }slot
inside the list block via therememberDragReorderStatehelper).
The state tracks:itemCount,draggingIndex,dragOffsetPx,
amutableStateMapOf<Int,Int>of item heights, and exposes
onDragStart/onDrag/onDragEnd/onDragCancel,
computeTargetIndex,shiftDirectionFor,itemHeightFor. - The
WaypointCardComposable now takes
index: Int, reorderState: DragReorderState?instead of the old
canMoveUp / canMoveDown / onMoveUp / onMoveDownparameters. - Card root applies
graphicsLayer.translationY(drag offset or
sibling shift),zIndexfor the dragged card, and
onGloballyPositionedto report height to the state.
- Added imports:
app/src/main/res/values/strings.xml- Added
drag_handle_cd = "Ziehgriff zum Verschieben". - Added
drag_reorder_hint_short = "Ziehgriff halten und verschieben"
(kept for future use; not displayed yet — heading stays compact).
- Added
app/build.gradle.ktsversionCode = 30,versionName = "2.0.2".
Why this approach (tradeoffs)
- Column, not LazyColumn. The Waypoint list shares the page's outer
verticalScroll. Switching the inner list to a LazyColumn inside a
scrolling Column is illegal (nested scroll) without significant
re-architecture of the whole page, which carries regression risk for
Atmo / PTT / map / draft / playlist features. The drag state is
therefore implemented over the existingColumnwith
onGloballyPositionedheight measurement andgraphicsLayer
translation — minimal invasiveness, no new dependencies. - Dedicated handle + long-press fallback. Using only long-press
delays the gesture; using only direct drag on the card surface
competes withverticalScroll. A dedicated handle with its own
pointerInputsolves both: page scrolling stays untouched, the
gesture starts immediately when the handle is grabbed, and
long-press is still available for users who don't realise the handle
exists. This is the same UX shape Google uses in Material reorder
lists. - No new external library. No
reorderable-compose, noandroidx.compose.foundationsnapshot
dependency; only stable Compose Foundation APIs already in BOM. - Persist on drop.
viewModel.moveWaypoint(from, to)is called
exactly once on drag end, so the existingrepository.saveAll
pipeline is invoked once per drag — same persistence semantics as
the v2.0.1 arrow taps. The half-neighbour-height threshold keeps the
drop position visually predictable. - Tour ordering dialog unchanged. The
Touren ordnencompact
dialog still uses▲▼arrows (task says this is acceptable; tours
list is short, dialog is rarely used).
Preserved features (regression checklist)
Verified by reading the source — no other files were touched:
applicationId = de.waypointaudio, signing config + keystore
properties path unchanged → upgrade-in-place from v2.0.0 / v2.0.1.- Wake-Lock settings (
Theme/MainActivity/WakeLockController):
untouched. - Map provider selection (
MapProvider,MapProviderDialog,
MapScreen): untouched. - Atmo layout (Begleitmusik mini-player above the track list,
BegleitmusikMiniPlayer+BegleitmusikDialog) and Atmo resume
manager: untouched. - Compact top bar /
ScrollableTabRowIndexOutOfBounds fix:
untouched (tabs.joinToString("|")key still in place). - Track-Editor / Drafts (
TrackDraftEditorScreen,
TrackDraftListScreen,TrackDraft,TrackRecordingManager):
untouched. - Backup import/export (
BackupImportManager,
BackupExportManager,ImportExportManager): untouched. - Map search (
PlaceSearchDialog,NominatimClient): untouched. - Audio library playlist (
TourMusicStore,
BackgroundMusicManager,BackgroundMusicPlayer): untouched. - PTT Atmo resume (
LivePttManager,LivePttService,
AtmoResumeManager): untouched. - OSM attribution: untouched.
- TabRow fix: untouched.
- About PayPal / version notice (
AboutDialog,
VersionNoticeDialog,VersionNoticeStore,AppVersion):
untouched.AppVersionreads version at runtime from the
PackageManager, so the About dialog will show2.0.2 (30)
automatically.
Regression checklist (manual smoke)
When installing the APK over v2.0.1 on a real device, please verify:
- App launches; About dialog reads
2.0.2 (30). - Tab row scrolls horizontally; adding / renaming / deleting tours
does not crash the tab row. - Waypoint list shows the DragIndicator handle on every card.
- Press handle, drag a card up/down past at least 2 neighbours, let
go — the order is updated and survives an app restart. - Long-press a card body (not the handle) for ~500 ms, drag, drop —
same result. - Start GPS service (▶ in top bar). Handles fade out; pressing them
or long-pressing a card does nothing. The hint
Sortieren während GPS/Aufzeichnung deaktiviertappears. - Stop GPS, start a track recording. Same disabled behaviour.
- While not reordering, normal vertical page scroll still works
(PTT → Atmo → manual player → track list → spacer). - Atmo mini-player still plays / pauses / next / prev.
- Manual player bar still plays / pauses / next / prev.
- Map screen opens, OSM tiles render, attribution still visible,
map provider dialog still works. - Track-Editor / drafts: open from FAB / overflow, recording flow
still works. - Backup import + export still produce/consume the same JSON / ZIP
shapes. - Map search dialog (Nominatim) still returns results.
- PayPal link in About still opens correctly.
Build / signature result
gradle :app:assembleRelease→ BUILD SUCCESSFUL in 47s.- Warnings: only pre-existing deprecations (
launchWhenStarted,
OSMDroid setters,Modifier.menuAnchor()). apksigner verify --print-certs:- Signer #1 DN matches v2.0.1.
- SHA-256 matches v2.0.1 (
7B:0E:A4:…:B3:BB:46).
aapt dump badging:package: name='de.waypointaudio' versionCode='30' versionName='2.0.2'sdkVersion:'26',targetSdkVersion:'35'application-label:'GPS2Audio'
Downloads
- Signed APK: