feat: auth screen with auto-redirect, sync favorites/history with backend

This commit is contained in:
nk
2026-06-02 19:12:07 +03:00
parent d4adb1e7be
commit a83672b455
2934 changed files with 97351 additions and 163 deletions

102
docs/superpowers/memory.md Normal file
View File

@@ -0,0 +1,102 @@
# radiOLA — Project Memory
## Overview
Android online radio app combining **Radio Record** (Russian dance radio network) with **Radio Browser API** (`radio-browser.info`) for genre filtering across thousands of internet radio stations.
**Stack:** Kotlin 2.0, Compose BOM 2024.06, Material3, Hilt 2.51.1, Media3 ExoPlayer 1.3.1, Room 2.6.1, Retrofit + Kotlinx Serialization, Coil 2.6.0, DataStore, Lucide icons.
---
## Architecture
Clean Architecture with these layers:
- `domain/` — models (`Station`, `Track`, `DeeplinkService`), repository interfaces, use cases
- `data/remote/` — Retrofit APIs (`RecordApi`, `RadioBrowserApi`), DTOs, mappers
- `data/local/` — Room (`AppDatabase` v2), DAOs (`StationDao`, `TagDao`, `TrackHistoryDao`)
- `data/repository/` — impls (`StationRepositoryImpl`, `RadioBrowserRepositoryImpl`, etc.)
- `di/` — Hilt `AppModule` (two Retrofit instances: Record + Radio Browser)
- `ui/` — Compose screens (`StationsScreen`, `HistoryScreen`, `SettingsScreen`, `PlayerBottomSheet`)
- `service/``PlayerController` (ExoPlayer singleton), `PlayerService` (`MediaSessionService`)
- `widget/``PlayerWidgetProvider` (4×1 home screen widget)
---
## Current State
### ✅ Working
- **Radio Record stations** load from `https://www.radiorecord.ru/api/stations/` → cached in Room
- **Stations grid** (`LazyVerticalGrid`, 2 columns) with cover, name, favorite heart
- **Playback** — real stream URLs via ExoPlayer, background service, notification, lock screen
- **Favorites** — Room toggle, context-aware next/prev (all vs favorites only)
- **History** — auto-saves tracks to `TrackHistoryRepository` on every `_currentTrack` change
- **Audio devices** — pauses on BT/wired/USB removal, resumes on reconnect
- **Deeplink buttons** — search current track in Yandex, Spotify, etc.
- **Genre chips** — `FilterChips` row with music genres only
- **Radio Browser integration** — loads stations by tag when VPN is active
- **Icy metadata parsing** for Radio Browser streams (`Artist - Song` format)
### ⚠️ Known Issues / Limitations
1. **Radio Browser API blocked** by some ISPs in Russia (IP `91.98.4.78` times out). App detects this and disables Radio Browser features gracefully. **Workaround: use VPN**.
2. **Radio Record API** does not return per-station tags. Tags are global (`StationsResult.tags`). We map them to Room `TagEntity` table via `TagDao`.
3. **Record station-to-genre mapping** is approximate — uses keyword matching on `tooltip`/`genre` field because API lacks structured genre data per station.
4. **Widget** uses `RemoteViews` with limited Compose support; `widget_player.xml` avoids AppCompat attrs.
5. **Deprecated `Divider`** in `HistoryScreen.kt:60` — should be `HorizontalDivider`.
6. **KSP `NonExistentClass`** — clean build required if Hilt constructor params change.
---
## Key Data Flows
### Station Loading
1. `StationsViewModel.init``refreshStationsUseCase()``StationRepositoryImpl.refreshStations()`
2. `RecordApi.getStations()` returns `StationsResponse(result = StationsResult(stations, tags))`
3. `StationDao.insertAll()` + `TagDao.clearAll()` + `TagDao.insertAll()`
4. `StationsViewModel.tags` combines `stationRepository.getTags()` + `_radioBrowserTags`
### Playback
1. `PlayerViewModel.play(station)``PlayerController.play(url, prefix, name)`
2. **Record source**`getNowPlayingUseCase(id)` polls `/api/stations/now/` every 10s
3. **Radio Browser source** → listens to `PlayerController.icyTitle` (ExoPlayer `onMediaMetadataChanged`)
4. `_currentTrack` updates → auto-saved to history + lock screen metadata via `updateMetadata()`
### Genre Filtering
- `tag == null` → show all Record stations
- `tag != null` → show Radio Browser stations for tag + Record stations whose `genre`/`name` matches keywords (`tagKeywords` map in `StationsViewModel`)
---
## Critical Code Rules
### ExoPlayer Lifecycle
`PlayerController` is `@Singleton`. **`PlayerService.onDestroy()` must NOT call `playerController.release()`** — releasing ExoPlayer kills its internal thread. Subsequent `play()` calls throw `IllegalStateException: dead thread` after OS restarts the service.
### Room Migrations
`AppDatabase` is version 2. `MIGRATION_1_2` creates `tags` table. Always add migration + bump version when adding entities.
### Radio Browser Null-Safety
`RadioBrowserStationDto` fields are nullable (`urlResolved`, `tags`, `country`, `codec`). Mapper falls back: `streamUrl = urlResolved ?: url ?: ""`.
---
## File Inventory
| File | Responsibility |
|------|----------------|
| `di/AppModule.kt` | Two Retrofit instances, Room DB with migration, repository bindings |
| `data/repository/StationRepositoryImpl.kt` | Fetch Record API, cache stations + tags in Room |
| `data/repository/RadioBrowserRepositoryImpl.kt` | Fetch Radio Browser stations/tags, swallow network errors |
| `ui/stations/StationsViewModel.kt` | Genre filtering, tag loading, search, Radio Browser availability flag |
| `ui/stations/StationsScreen.kt` | Grid, chips, search bar, empty state with "Show All" button |
| `service/PlayerController.kt` | ExoPlayer, audio device callback, Icy title capture, MediaSession wrapper |
| `service/PlayerService.kt` | Background service, MediaSession, notification, lock screen |
| `ui/player/PlayerViewModel.kt` | Playback orchestration, now playing polling, Icy parsing, playlist |
| `data/remote/dto/RadioBrowserDto.kt` | DTOs with nullable fields for Radio Browser API |
| `data/local/AppDatabase.kt` | Room v2 with `StationEntity`, `TrackHistoryEntity`, `TagEntity` |
---
## Next Steps / Open Items
- [ ] Replace deprecated `Divider` with `HorizontalDivider` in `HistoryScreen`
- [ ] Add empty-state illustration or better UX when Radio Browser is unavailable
- [ ] Consider caching Radio Browser stations per tag in Room for offline reuse
- [ ] Evaluate if `tagKeywords` mapping for Record stations needs tuning per user feedback
- [ ] Add error retry / exponential backoff for Radio Browser API calls when VPN is unstable