83 Commits

Author SHA1 Message Date
cottongin
a09c50c302 docs: refresh README with screenshots, full feature list, and build guide
Add AI disclaimer, 4 emulator screenshots, expanded feature sections
(Android Auto, SomaFM, per-station quality, tabs, Media3 notification,
now-playing logging, blurred art background), build.sh documentation,
inline architecture diagram, and tech stack table.

Made-with: Cursor
2026-03-18 14:06:00 -04:00
cottongin
639cb99d1f chore: add ProGuard keep rules and MIT license
Add app/proguard-rules.pro with keep rules for Room entities/DAOs,
Media3 service and player classes, Coil BlurTransformation, and Kotlin
coroutines internals. Add MIT LICENSE file.

Made-with: Cursor
2026-03-18 13:41:31 -04:00
cottongin
e03e32183b build: enable minification for release builds
Enable isMinifyEnabled and add proguardFiles for the release build type.

Made-with: Cursor
2026-03-18 13:36:04 -04:00
cottongin
7cd9d249d8 docs: add implementation plan for heavy blur background
Made-with: Cursor
2026-03-18 13:35:57 -04:00
cottongin
a5057be3c7 chore: remove Cloudy blur library dependency
Made-with: Cursor
2026-03-18 11:49:33 -04:00
cottongin
bb35ec8a8b feat: replace cloudy blur with BlurTransformation on player background
Made-with: Cursor
2026-03-18 11:44:44 -04:00
cottongin
14aeeecd9c fix: guard zero-dimension bitmap and recycle on early return in BlurTransformation
Made-with: Cursor
2026-03-18 11:36:35 -04:00
cottongin
2e615850bc feat: add Stackblur BlurTransformation utility
Made-with: Cursor
2026-03-18 11:23:30 -04:00
cottongin
ada81dddd0 docs: add design doc for heavy blur background replacement
Replace Cloudy with Coil BlurTransformation for unblurred album art issue.

Made-with: Cursor
2026-03-18 11:13:16 -04:00
cottongin
5105794120 docs: add Android Auto testing summary and ignore apkm files
Documents all DHU testing attempts, root causes for failures (DHU v2.0
protocol incompatibility with AA v16+), and lessons learned. Adds
*.apkm to .gitignore to avoid accidentally committing large APK bundles.

Made-with: Cursor
2026-03-18 10:19:59 -04:00
cottongin
d451d005c0 fix: register session with addSession() to activate Media3 notifications
MediaSessionService only sets up its internal MediaNotificationManager
when addSession() is called. This normally happens when a MediaController
binds, but since we use custom intents, no controller ever connected.
Calling addSession() after session creation activates the notification
pipeline. Also removed debug logging.

Made-with: Cursor
2026-03-18 06:58:55 -04:00
cottongin
5126ad6366 fix: post placeholder foreground notification immediately on play
startForegroundService() requires startForeground() within ~5 seconds.
The async coroutine (DB queries, old job join) delayed the Media3
notification pipeline past this deadline. Now we:
- Create the media session in onCreate() instead of lazily
- Post a minimal "Connecting…" notification synchronously in
  onStartCommand before launching the async play coroutine
- Media3 replaces it with the proper media notification once the
  adapter state updates to Playing

Made-with: Cursor
2026-03-18 06:41:24 -04:00
cottongin
f65b0db0f4 fix: propagate ICY metadata to MediaItem and remove unsafe stopSelf
- updateMetadata() now also updates _currentMediaItem so track info
  and artwork flow through to the notification/lockscreen via getState()
- Remove else -> stopSelf() from onStartCommand to avoid killing the
  service on internal MediaLibraryService intents

Made-with: Cursor
2026-03-18 06:14:49 -04:00
cottongin
00f4da20b6 feat: rewrite RadioPlaybackService as MediaLibraryService with browse tree
Made-with: Cursor
2026-03-18 06:10:17 -04:00
cottongin
04225de888 feat: add RadioPlayerAdapter — thin Player facade for Media3
Made-with: Cursor
2026-03-18 05:58:09 -04:00
cottongin
39653462d1 feat: add Android Auto automotive_app.xml and update service manifest
Made-with: Cursor
2026-03-18 05:56:38 -04:00
cottongin
effb498896 chore: swap androidx.media for media3-session + media3-common
Made-with: Cursor
2026-03-18 05:55:32 -04:00
cottongin
da754b3874 Add implementation plan for Media3 migration + Android Auto
8 tasks: deps, manifest, PlayerAdapter, service rewrite,
NotificationHelper deletion, controller check, build/test, cleanup.

Made-with: Cursor
2026-03-18 05:53:48 -04:00
cottongin
cfc845479b Add design doc for Media3 migration + Android Auto support
Covers architecture for replacing MediaSessionCompat with Media3
MediaLibraryService, RadioPlayerAdapter facade, browse tree for
Android Auto, and notification improvements.

Made-with: Cursor
2026-03-18 05:50:40 -04:00
cottongin
a978905ea6 Add RadioDatabase schema v6
Made-with: Cursor
2026-03-18 05:25:38 -04:00
cottongin
d5dc2d81bd fix: scroll only until text exits screen
Was scrolling an extra container-width of invisible distance after the
text had already left the screen, adding ~11s of dead time. Now scrolls
to -textWidthPx (text just off-screen) instead of -(textWidthPx +
containerWidthPx).

Made-with: Cursor
2026-03-18 05:18:53 -04:00
cottongin
600687ab56 feat: add fade-in and shorten restart pause for ticker
Text now fades in over 300ms when it reappears instead of popping in.
Restart pause shortened from 1.5s to 0.5s. Initial 1.5s delay kept.
Switched from infiniteRepeatable to Animatable + LaunchedEffect loop
for independent control over initial vs subsequent cycles.

Made-with: Cursor
2026-03-18 04:10:23 -04:00
cottongin
9bd38224b1 feat: replace bounce marquee with constant-speed ticker
Replaces BounceMarqueeText with TickerText that scrolls at a fixed
33 dp/s regardless of text length. Text scrolls left off-screen with
a container-width gap before looping. Initial 1.5s delay lets the
user read the beginning.

Made-with: Cursor
2026-03-12 05:52:42 -04:00
cottongin
5d551c9380 Add ticker text implementation plan
Two-task plan: replace BounceMarqueeText with TickerText composable,
then manually verify on device.

Made-with: Cursor
2026-03-11 18:24:11 -04:00
cottongin
202a631897 Add ticker text design doc
Captures the design for replacing BounceMarqueeText with a
constant-speed TickerText composable (33 dp/s, continuous leftward
scroll with gap looping).

Made-with: Cursor
2026-03-11 18:22:23 -04:00
cottongin
a4c0fa9a6e fix: resolve playback stop failure and Icecast stream disconnection
AudioEngine.stop() only called Thread.interrupt(), which doesn't
interrupt blocking InputStream.read() on OkHttp streams. This caused
audio to continue after stop and blocked subsequent play attempts
(old job never completed). Now closes timedStream to force the
blocking read to fail.

Removed LowLatencySocketFactory (16KB receive buffer) which triggered
Icecast slow-client disconnection on burst-on-connect. Force HTTP/1.1
to avoid HTTP/2 negotiation issues with Icecast servers.

Also fixed: awaitEngine() SharedFlow collector coroutine leak, and
added MAX_CATCHUP_FRAMES safety cap to prevent infinite frame skipping.

Made-with: Cursor
2026-03-11 18:14:13 -04:00
cottongin
8872b9de96 fix: resolve tab long-press context menu blocked by drag gesture
The detectDragGesturesAfterLongPress modifier was consuming the
long-press event before combinedClickable.onLongClick could fire.
Replace combinedClickable with movement-tracking inside the drag
handler — small movement on release shows the context menu, large
movement commits the reorder. Move tap handling into Tab.onClick.

Made-with: Cursor
2026-03-11 17:12:14 -04:00
cottongin
5f7cb050e5 fix: address code review issues — pinned on fresh install, reorder selection, stale rename, detached scopes, duplicate click
Made-with: Cursor
2026-03-11 16:32:33 -04:00
cottongin
5c87f821e0 feat: add drag-to-reorder for tabs within pinned/unpinned groups
Made-with: Cursor
2026-03-11 16:30:00 -04:00
cottongin
2d3b0cea7a feat: add tab rename and pin/unpin via long-press context menu
Made-with: Cursor
2026-03-11 16:27:13 -04:00
cottongin
973bb9e7fe feat: add playlist naming dialog on import
Made-with: Cursor
2026-03-11 16:25:58 -04:00
cottongin
eb111fce35 feat: add pinned column to Playlist with migration
Made-with: Cursor
2026-03-11 16:25:56 -04:00
cottongin
cf57d22dc5 feat: add per-station quality UI — context menu and dialog
- Create QualityOverrideDialog with radio selection for preferred quality
- Add Quality menu item to station long-press (only for stations with streams)
- Add setQualityOverride, getStreamsForStation, getQualityOverrideForStation to ViewModel
- Add getStationIdsWithStreams to StationStreamDao for hasStreams lookup

Made-with: Cursor
2026-03-11 16:22:59 -04:00
cottongin
92f3b35418 Task 8: Wire StationPreference into StreamResolver
- Add StationPreferenceDao to StreamResolver constructor
- Check station_preferences before station.qualityOverride / global prefs
- Mock stationPrefDao in StreamResolverTest, add precedence test

Made-with: Cursor
2026-03-11 16:20:21 -04:00
cottongin
2087b48e4e Task 7: Create StationPreference entity and DAO
- Add StationPreference entity with FK to Station, unique index on stationId
- Add StationPreferenceDao with getByStationId, upsert, deleteByStationId
- Add station_preferences table via MIGRATION_4_5

Made-with: Cursor
2026-03-11 16:20:14 -04:00
cottongin
d0443dfd85 Add StreamResolver unit tests for URL ordering logic
Made-with: Cursor
2026-03-11 16:16:48 -04:00
cottongin
98730959dd fix: add navigation bar padding to miniplayer
Made-with: Cursor
2026-03-11 16:16:09 -04:00
cottongin
3240db829f Refactor RadioPlaybackService: transition(), URL fallback, fixed finally
- Add transition() - all state changes route through it
- Add ConnectionFailedException for connection failures
- Refactor startEngine to accept urls: List<String>, iterate with Connecting state
- Extract awaitEngine() for event collection
- Set Connecting (not Playing) initially; Playing only on AudioEngineEvent.Started
- Fix handlePlay finally block: handle all states (Paused vs else)
- Update reconnectLoop to resolve URLs inside loop
- Add Service import for STOP_FOREGROUND_REMOVE

Made-with: Cursor
2026-03-11 16:14:27 -04:00
cottongin
0d5992f4a3 test(PlaybackState): add state machine transition tests
- Idle → Connecting, Connecting → Playing/Paused/Reconnecting/Idle
- Playing → Paused/Reconnecting/Idle
- Paused → Connecting
- Reconnecting → Connecting/Idle
- Stop from any state returns to Idle

Made-with: Cursor
2026-03-11 16:11:14 -04:00
cottongin
f3d28f1a52 refactor(PlaybackState): add URL context to Connecting state
- Add urls: List<String> = emptyList() for URL fallback list
- Add currentUrlIndex: Int = 0 for tracking fallback progress
- Defaults preserve backward compatibility (Connecting(station) still works)

Made-with: Cursor
2026-03-11 16:11:12 -04:00
cottongin
7da9fe9b5b fix: enable cleartext HTTP for radio streams
Made-with: Cursor
2026-03-11 16:09:54 -04:00
cottongin
e1286911cc fix: disable NullSafeMutableLiveData lint check crashing release builds
The lintVitalAnalyzeRelease task fails with an IncompatibleClassChangeError
in NonNullableMutableLiveDataDetector, which is a known bug in the lint
library. Disabling this specific check unblocks release APK builds.

Made-with: Cursor
2026-03-11 16:09:22 -04:00
cottongin
a3a00582a0 docs: add playback and UI fixes implementation plan
14 tasks covering cleartext HTTP, state machine refactor, URL
fallback, miniplayer insets, per-station quality, and playlist
management (import naming, rename, pin/unpin, drag reorder).

Made-with: Cursor
2026-03-11 16:08:00 -04:00
cottongin
0705e6a2bc docs: add playback and UI fixes design
State machine refactor of RadioPlaybackService, cleartext HTTP,
URL fallback, miniplayer insets, per-station quality prefs,
and playlist management (import naming, rename, pin/unpin, reorder).

Made-with: Cursor
2026-03-11 16:03:59 -04:00
cottongin
eac81df4b0 fix: show last 20 lines on build failure and unhide clean errors
Build failures now display a colored error banner with the last 20
lines of Gradle output. Removed stderr suppression from clean_build
so Gradle errors are visible.

Made-with: Cursor
2026-03-11 04:38:28 -04:00
cottongin
741b8cb30a feat: add debug build and clean commands
Made-with: Cursor
2026-03-11 04:32:54 -04:00
cottongin
1e5ba4e4fa feat: add release build with signing
Made-with: Cursor
2026-03-11 04:30:50 -04:00
cottongin
ec21256ca5 fix: surface keytool errors and add keytool preflight check
Made-with: Cursor
2026-03-11 04:29:45 -04:00
cottongin
b8a4ddf4a0 feat: add keystore creation and management
Made-with: Cursor
2026-03-11 04:26:58 -04:00
cottongin
1825b7930e fix: harden get_version_name against missing file and extraction failures
Made-with: Cursor
2026-03-11 04:25:48 -04:00