Commit graph

34 commits

Author SHA1 Message Date
avi
80b0300141 feat(discover): browse sub-genres Wavlake doesn't index via radio-tag station search
Wavlake rankings 400 on sub-genres (Trance, Classic Rock, Metal...). Discover
chips now include 15 sub-genres; tapping one routes through refreshRankings ->
browseRadioGenre, filling a new browseStations StateFlow from radio-browser
tag search (most-listened first) rendered by RadioGenreList. Wavlake-indexed
genres keep the rankings path.

Genre matching is now space-insensitive (user types 'oldschool' for 'Old
School Hip Hop'). radio-browser tag match is case-SENSITIVE against lowercase
stored tags — byTag() lowercases or every sub-genre browse returns zero hits.
2026-10-01 18:11:59 -05:00
avi
31d47f4cb5 fix(discover): genre card from search results did nothing — openGenre reloaded rankings but the view stayed in the Results branch so the update was invisible. Clear the query and return to Idle rankings view, showing 'Top in <genre>'. 2026-10-01 10:44:30 -05:00
avi
598556dbdd fix(crash): home button painted adaptive mipmap.ic_launcher — on API 26+ that resolves to the adaptive-icon XML and painterResource throws IllegalArgumentException ('Only VectorDrawables and rasterized asset types'), killing the app on first frame. Add rasterized ic_home_mark PNGs and paint those instead; bump to 0.1.2 (versionCode 3). 2026-10-01 10:33:15 -05:00
avi
90afcf3b6a feat(icon): adaptive launcher icon with rounded legacy fallbacks
The launcher icon was a flat square PNG with opaque black corners, so it
rendered with sharp edges. Extracted the artwork onto a proper adaptive
icon (solid #111 background layer, artwork centered in the safe zone,
monochrome layer for themed icons) and re-cut the legacy mipmaps with
rounded corners for pre-26 launchers.
2026-10-01 10:09:05 -05:00
avi
b18f2df1f4 feat(update): in-app self-update — check Forgejo latest release, download APK with progress, launch installer via FileProvider; REQUEST_INSTALL_PACKAGES + unknown-sources redirect; version-compare unit tests 2026-09-30 12:10:22 -05:00
avi
6147e0fde2 fix(playback): ghost ICY readers — stop() racing a blocked read fired onError, whose auto-restart resurrected unbounded zombie streams; every ghost flickered its own song over the now-playing field
- IcyStreamReader: requestedStop flag — intentional stops never report as errors
- PlaybackManager.startIcyReader: always stop the previous reader first; all four listener callbacks ignore events from a replaced reader (ghost audio was also poisoning the rolling buffer)
2026-09-30 11:03:05 -05:00
avi
a490b394d3 fix(library): overflow menu for refresh/delete/multi-select so title keeps full width; song-count plural 2026-09-30 10:23:21 -05:00
avi
9e84906715 feat(discover): type-toggle chips (Artists/Tracks/Genres/Stations), bucketed results, local station + genre search 2026-09-30 10:23:21 -05:00
avi
bd4f6bb874 perf: cache WaveGraphic paths per size (was rebuilding ~3k lineTo per draw frame) + measured motion pass 2026-09-28 20:41:37 -05:00
avi
e5a64e1892 feat: new launcher icon + motion polish (adjacent-tab glide, animateItem rows, header crossfade, cheaper pressDip) 2026-09-28 19:25:29 -05:00
avi
36f61ddb40 Checkpoint all work; ignore build artifacts and gradle caches 2026-09-23 18:58:21 -05:00
avi
b000544d15 perf(app): fluid navigation + genre search
Directory search now merges name and tag(genre) hits so genre queries
find tagged stations. Tabs jump instantly (adjacent glide, swipe kept);
startup pre-builds disk-backed singletons off main thread; equalizer
animation runs only on the playing indicator; row placement animations;
missing list key. Debug-only StrictMode guard for main-thread I/O.
Release signed with debug key for direct installs.
2026-09-10 06:48:55 -05:00
avi
1c9d07251a feat(nostr): Amber external-signer login, pubkey import, ephemeral zap fallback
NIP-55 get_public_key flow (nostrsigner: intent) with signer package
stored for future requests; manual npub import fallback; sign-out.
Rename user-facing Decentralized labels to Nostr. Fix Wavlake track
zaps for external-signer logins via canSignLocally + ephemeral keys.
Artist address resolution + zap buttons in Discover search.
2026-09-10 02:17:48 -05:00
avi
5ff5318db4 feat(nostr): decentralized layer Phase A — identity, relays, zaps, NWC wallet
Additive Wavlake-inspired layer; zero existing features touched.

Crypto/protocol (pure-Kotlin core, fr.acinq secp256k1-kmp 0.17.3):
- Bech32 + NIP-19 (npub/nsec/nprofile/nevent/naddr)
- NIP-01 events: canonical serialization, BIP-340 schnorr sign/verify
- NIP-04 ECDH AES-CBC (raw x-coord via pubKeyTweakMul)
- Wavlake music NIP kinds 30440-30443 (Track/Album/Artist/Playlist)
- 4 conformance unit tests vs official spec vectors (all pass)

NostrService: multi-relay OkHttp websocket client, publish/subscribe/
query/getProfile/publishProfile/publishReaction/publishTrack/publishRaw,
best-effort failure posture (relays down = silent, app unaffected).

Identity: NostrKeys — AndroidKeyStore-encrypted private key at rest,
npub-only display. Verified on-device: create → npub chip → survives
force-stop + relaunch.

Payments (PaymentService abstraction, NWC provider):
- NIP-47 Nostr Wallet Connect over the pairing relay, pairing string
  keystore-encrypted, 15s timeout, never hangs playback
- LUD-06/LUD-16 lightning-address invoice resolution
- NIP-57 zaps: ephemeral-key zap request → callback nostr= → pay
- payment history records (no secrets)

UI (design-system tokens, progressive disclosure):
- ⚡ bolt on mini-player → TipSheet (21/100/500/1000/custom, recent
  addresses)
- Appearance → ⚡ Decentralized sheet: identity create/import, NWC
  connect, advanced relays/profile
2026-09-08 11:49:09 -05:00
avi
e2ed74a47d feat(ui): mini-player buffering pose + one-tap retry on error
- Equalizer chip dims while BUFFERING/INITIALIZING (no false 'playing'
  signal during reconnect)
- On ERROR the bar's subtitle becomes 'Couldn't reach · Retry' —
  tapping replays the current station without leaving the tab
- NowPlayingViewModel.retry() re-plays current URL
2026-09-08 10:13:06 -05:00
avi
918aeaf8d3 feat(ui): swipe-down to dismiss the mini-player
- verticalDragToDismiss: bar follows the finger downward
  (translationY, capped 120dp) and dismisses past 40% height or on a
  downward fling; springs back otherwise; static under Reduce Motion
- Dismissal is per-source: playback continues, and the bar returns
  automatically when a different station/song starts
- Tabs route now owns its mini-player (root no longer stacks one)
- On-device: swipe dismissed bar while PLAYING; station change
  re-showed it
2026-09-08 10:07:25 -05:00
avi
172e932ef3 feat(ui): press feedback + haptics
- pressDip modifier (scale 0.97 press / spring-back, transform-only,
  reduce-motion aware) sharing the row's interaction source with the
  Material ripple
- Library song rows: ripple + dip on tap; verified tap still plays
- Page-settle haptic tick in the tab pager (skipped on first frame)
2026-09-08 10:02:35 -05:00
avi
d612ba6f49 feat(nav): swipeable pager for the 4 tabs (Spotify-style)
- New TabsScreen: HorizontalPager (Stations · Library · Streams ·
  Playlists) synced with a shared bottom NavigationBar; swipes follow
  the finger, tab taps animate to page, neighbors pre-composed so
  pages and their ViewModels survive page changes
- Light haptic tick when a new page settles (skipped on first frame)
- Mini-player sits above the nav bar inside the pager surface:
  playback controls reachable from every tab
- Hub tab buttons navigate to tabs?index=N; playlist selection from
  the Playlists page animates to Library and deep-links the playlist
- All previous routes preserved (stations/library/streams/playlists
  still resolve); detail transitions now direction-aware via
  Motion.kt forward/backward params
2026-09-08 09:59:21 -05:00
avi
eb32aed07c fix(ui): mini-player attributes track to its station
Radio subtitle is now 'artist · Station' so a track name never looks
like it contradicts the station shown in the list/hub.
2026-09-08 09:39:26 -05:00
avi
4f8d3e9299 feat(ui): Spotify-style playback signatures
- EqualizerBars: animated 3-bar 'now playing' indicator (out-of-phase
  pulses; frozen mid-height when paused; static under Reduce Motion)
- Now-playing bar: two-line layout with live metadata (title + artist
  for streams and local songs) and rise transitions; equalizer glyph
  replaces the icon chip while playing
- Playing state in every list: Stations card, Streams card, and
  Library song rows show the animated equalizer instead of a static
  icon on the active source
- NowPlayingViewModel exposes stream/local metadata flows
2026-09-08 09:35:53 -05:00
avi
5b524f2153 fix: dedupe library — refuse duplicate saves, auto-remove existing dupes
Root cause: import deduped by artist+title, but save-from-air did not,
so saving the same broadcast again wrote 'Song (1).mp3' + a new row.

- SaveSongUseCase now refuses a save when artist+title already exists
  (case-insensitive), reporting 'Already in your library'
- DedupeLibrary collapses existing duplicates: keeps the largest file
  (ties → oldest), re-points playlist memberships to the keeper, then
  deletes duplicate rows + files
- Runs automatically when the Library opens; a snackbar reports the
  removed count
- On-device: user library 89 → 54 songs, 0 remaining dup groups,
  playlist links intact (Zaza All 32, Apollo 19), zero orphans, all
  kept files present on disk (pre-run backup taken)
2026-09-08 09:18:25 -05:00
avi
ec55ba84ae feat: sleep timer, playback speed, real queue with shuffle/repeat
- Sleep timer: 15/30/45/60/90 min, live countdown chip, pauses
  playback on expiry (station stays loaded); cancel from dialog
- Playback speed 0.5x-2x for stations and saved songs; persists
  across track changes; chip shown when != 1x
- Local playback now uses an ExoPlayer media queue (library list from
  tapped song, shuffle option): native advance at track end, transport
  next/prev walk the queue, repeat off/all/one
- Player transport routes queue-first: local playback advances the
  queue, radio cycles stations; repeat/shuffle dimmed for radio
- Library 'now playing' highlight follows queue transitions via
  currentLocalTrackIdFlow; dead manual shuffle-index code removed
- Verified on-device: speed 1.25x confirmed in media_session, sleep
  dialog + countdown, save-from-air picker, radio option states
2026-09-08 01:30:43 -05:00
avi
ee0d6b6149 polish: remove unused imports, tidy BorderStroke qualification 2026-09-08 00:45:18 -05:00
avi
bc6396a570 feat: directory search, favorites, motion system, home button, transport fix
- radio-browser.info directory search (debounced, ~58k stations) with
  codec/bitrate/country metadata and save-to-stations action
- station favorites (star) persisted in StationRepository; Starred
  section pinned in Stations; active-station row highlight
- motion system (Motion.kt): shared-axis nav transitions, hub hop,
  metadata rise, play/pause pop; LocalReduceMotion honors system
  'remove animations'
- AppHomeButton (launcher icon) top-left on every surface + SatsAmp
  wordmark on the hub; home pops the stack back to the player
- transport row: add missing Next button, conventional order
  stop · prev · play · next
- WaveGraphic center glow: wider, fainter, no perceptible disc edge
- StreamMetadata: strip unsubstituted {{template}} residue
- PRODUCT.md + DESIGN.md (impeccable design context); .gitignore for
  re-downloadable skill engine binaries
2026-09-08 00:37:33 -05:00
avi
1b7c6febbb feat: rename app to SatsAmp + appearance themes (violet/blue/green/orange accents, System/Dark/Light modes)
- project dir ~/Projects/RadAmp -> ~/Projects/SatsAmp; classes, strings, launcher label, UA strings renamed; applicationId + radamp.db kept so existing installs/data survive
- UiPrefs (SharedPreferences) + UiTokens: 4 accents x dark/light, every text/accent pair AA-verified (accentOnFill picks ink vs white per accent)
- legacy Premium* colors rewritten to LocalTokens.current reads; theme switches live, persists across restart
- Appearance dialog in Library top bar (swatches + System/Dark/Light), verified on-device: orange live-switch, light mode + persistence, folder-tab regression pass
2026-09-07 18:45:29 -05:00
avi
6628965e68 wip: glass theme tokens + demo page + charcoal base port 2026-09-07 18:12:09 -05:00
avi
a99d4c43f4 fix: folder tab long-press via Initial-pass pointerInput watcher + shared Handler timer; taps still select (verified on-device: 8-step gesture suite) 2026-09-07 13:57:49 -05:00
avi
3f84249c82 fix: folder tabs selectable again — long-press via pointerInput/detectTapGestures instead of combinedClickable (Tab's selectable masked the modifier's onClick) 2026-09-07 12:38:57 -05:00
avi
38b36687ee feat: library UX — long-press folder tab to delete (group only or +songs), import dedupe skips duplicates, always-visible sort row with A-Z/Z-A toggle 2026-09-07 12:29:58 -05:00
dev
e6f88d5740 feat: folder import creates a folder-named playlist (Library tab) + Imported/<folder>/ subdir 2026-09-07 11:44:12 -05:00
dev
153a132656 ux: home 'Import music' opens import dialog directly instead of navigating to Library 2026-09-07 11:26:36 -05:00
dev
c0aea3eabc fix: folder import — treat picked tree URIs via fromTreeUri (was misread as file -> Invalid URI) 2026-09-07 11:21:27 -05:00
dev
7372d30943 feat: manual song import via SAF picker (copy to library + tag read + dedupe) 2026-09-07 11:08:57 -05:00
dev
72e55ba319 baseline before import-songs feature 2026-09-07 11:04:09 -05:00