|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +Guidance for AI coding agents working on this repository. |
| 4 | + |
| 5 | +## Project overview |
| 6 | + |
| 7 | +**DatMusic** — a third-party music search/streaming client for the DatMusic service (datmusic.xyz). |
| 8 | +Android app, Jetpack Compose UI, published on Google Play as `tm.alashow.datmusic`. |
| 9 | +Closed-source, single-developer project (Alashov Berkeli, `tm.alashow`). |
| 10 | + |
| 11 | +- API base: `https://api-demo.datmusic.xyz/` (defined in `modules/core-domain/src/main/java/tm/alashow/Config.kt`) |
| 12 | +- Current version: see `buildSrc/src/main/java/tm/alashow/buildSrc/App.kt` (versionCode/versionName live there) |
| 13 | +- Play listing data: `app/src/main/play/` (title, description, screenshots, subscription products) |
| 14 | +- App store art assets: `art/` (screenshots source/framed, feature graphic psd, dependency graph) |
| 15 | + |
| 16 | +## Tech stack |
| 17 | + |
| 18 | +- Kotlin (currently a beta version — check `gradle/libs.versions.toml`), Gradle 9.x, AGP 9.x |
| 19 | +- Java 11 source/target compatibility; CI builds with JDK 21 |
| 20 | +- Jetpack Compose (dual BOM: stable + alpha in `libs.versions.toml`), Material3 |
| 21 | +- Hilt (Dagger 2) for DI; KSP for Hilt + lifecycle codegen |
| 22 | +- Navigation Compose + Accompanist modal bottom sheets |
| 23 | +- Room 2.8 (single `AppDatabase` in `:modules:core-data`), incl. FTS table for audio |
| 24 | +- Retrofit + OkHttp + kotlinx-serialization (API in `DatmusicEndpoints`) |
| 25 | +- Media3 ExoPlayer (playback), Fetch (downloads), Store5 (caching/preferences) |
| 26 | +- Firebase: Crashlytics, FCM, Remote Config, Analytics; billing via Qonversion |
| 27 | +- Coil (images), Timber (logging), threeTenAbp (dates) |
| 28 | +- Testing: JUnit4, Truth, Robolectric, MockK, Turbine |
| 29 | +- Code style: Spotless + ktlint 0.41.0 (android mode, max line length 200, wildcard imports allowed), license header required on all `.kt` files |
| 30 | + |
| 31 | +## Repository layout |
| 32 | + |
| 33 | +Multi-module Gradle project. `settings.gradle` is the source of truth for the module list. |
| 34 | +Dependency direction (top depends on bottom): |
| 35 | + |
| 36 | +``` |
| 37 | +app → ui-* (feature screens), core-ui* (shared UI), navigation, core-playback |
| 38 | +core-ui-* → core-ui, common-*, navigation |
| 39 | +core-playback / core-library / core-downloader / core-media → core-data → core-domain |
| 40 | +core-data → common-data → common-domain → base |
| 41 | +base-android → base → i18n |
| 42 | +``` |
| 43 | + |
| 44 | +### Modules (all under `modules/`) |
| 45 | + |
| 46 | +**Foundation** |
| 47 | +- `base` — platform-agnostic utils: `Analytics`, date utils, extensions, `SnackbarManager`, `CoroutineDispatchers`, serializers. Re-exports `i18n` resources. |
| 48 | +- `base-android` — Android-specific: Qonversion billing (`billing/`), Coil image loading (`imageloading/`), app initializers (Timber, threeTenAbp), `ViewState` base UI. |
| 49 | +- `i18n` — **all UI strings live here** (`src/main/res/values*/app_strings.xml`, `base.xml`). Locales: en (default), ru, tk, tr, fr, es, de. Also `UiMessage`, `Validation`, `TextCreator`. |
| 50 | +- `common-domain` — shared domain models: `Async`, `InvokeStatus`, `Optional`, `Resource`, `BaseEntity`, `SortOption`, `Params`. |
| 51 | +- `common-data` — shared data infrastructure: `Interactor` base class (see below), `PreferencesStore`, `RemoteConfig`, `RoomRepo`, `DatabaseTxRunner`, `LocalFilesRepo`, `LastRequests`. |
| 52 | +- `common-testing` / `common-testing-instrumented` — `BaseTest`, `TestImageModule`, Turbine extensions. |
| 53 | + |
| 54 | +**Core (business logic, no UI)** |
| 55 | +- `core-domain` — DatMusic entities: `Audio`, `Artist`, `Album`, `Playlist`, `DownloadRequest`, `Genre`, `LibraryItem`; `ApiResponse`, API errors; **`tm.alashow.Config`** (BASE_URL, API_BASE_URL, timeouts, Play Store id). |
| 56 | +- `core-data` — Room `AppDatabase` + DAOs (`daos/`: Audios, AudiosFts, Artists, Albums, Playlists, PlaylistsWithAudios, DownloadRequests), migrations (`db/Migrations.kt`), Retrofit `DatmusicEndpoints` + `ApiModule`, FCM registration interactor. |
| 57 | +- `core-media` — artist/album/audio/search data: repos, data sources, Store-based caching, paging sources, interactors (`interactors/`), observers (`observers/`). |
| 58 | +- `core-library` — playlists: `PlaylistsRepo`, playlist interactors (create/update/delete/download/addTo), backup & restore (`interactors/backup/`), FTS migration. |
| 59 | +- `core-downloader` — Fetch-based `DownloaderImpl`, `DownloadManager`, `DownloadRequestsRepo`, download observers/messages/notifications. |
| 60 | +- `core-playback` — Media3: `PlayerService` (media session), `DatmusicPlayer`/`AudioPlayer`, `MediaQueueBuilder`, audio focus, media button & becoming-noisy receivers, `PlaybackConnection`. |
| 61 | + |
| 62 | +**UI** |
| 63 | +- `common-compose` — Compose utilities: `rememberFlowWithLifecycle`, composition locals (`LocalAnalytics`, `LocalSnackbarHostState`, `LocalIsPreviewMode`), preview wrappers (`CombinedPreview`, `LocalePreview`, `FontScalePreview`, `DevicePreview`), Recompose Highlighter. |
| 64 | +- `common-ui-theme` — `AppTheme`, `ThemeViewModel`, color/typography/shape specs, Material bridges. |
| 65 | +- `common-ui-components` — generic composables: swipes, material wrappers, list items. |
| 66 | +- `core-ui` — cross-feature UI glue: `AudioActionHandler`, `AudioDownloadItemActionHandler`, snackbar host, **preview fakes** (`ui/previews/Preview*.kt`) used by previews in every other module. |
| 67 | +- `core-ui-media` — shared artist/album/audio list UI (items used in search results etc.). |
| 68 | +- `core-ui-playback` — mini player controls, playback sheets UI. |
| 69 | +- `core-ui-downloader` — download progress UI components. |
| 70 | +- `core-ui-library` — playlist UI (add-to-playlist dialog etc.). |
| 71 | +- `ui-search` — search screen (root tab). |
| 72 | +- `ui-artist` — artist details screen. |
| 73 | +- `ui-album` — album details screen. |
| 74 | +- `ui-library` — library root tab + playlist create/edit/detail screens. |
| 75 | +- `ui-downloads` — downloads root tab + per-audio download UI. |
| 76 | +- `ui-settings` — settings root tab: theme, backup/restore, premium (Qonversion). |
| 77 | +- `navigation` — `Navigator`/`NavigatorViewModel`, bottom-sheet navigator, **`screens/Screens.kt`** (all root + leaf routes, args, deep links). |
| 78 | + |
| 79 | +**App shell** (`app/`) |
| 80 | +- `ui/DatmusicApp.kt` — composition root (theme → navigator → downloader host → playback host → action handlers → content). |
| 81 | +- `ui/home/Home.kt` — bottom nav / wide-layout nav rail + mini player + scaffold. |
| 82 | +- `ui/MainActivity.kt`, `ui/Intents.kt` (deep links), `di/AppModule.kt`, `di/NetworkModule.kt`. |
| 83 | +- `fcm/`, `notifications/`, `util/` (Remote Config init, OkHttp interceptors). |
| 84 | +- Launcher icons in `src/main/res-drawable/` (extra res dir, see `sourceSets` in `app/build.gradle`). |
| 85 | + |
| 86 | +## Architecture & patterns |
| 87 | + |
| 88 | +- **Layers**: Compose screen → Hilt ViewModel (exposes `Flow` of state) → Interactor/Repo → DataSource (API via Retrofit / Room DAO / Store cache) → domain entities in `core-domain`. |
| 89 | +- **`Interactor`** (`common-data/.../Interactor.kt`) is the base for one-shot use cases: `invoke(params): Flow<InvokeStatus>` with timeout + `InvokeStarted/Success/Error` emissions; `execute(params)` for fire-and-forget. |
| 90 | +- **Observers** are long-lived `Flow` sources (e.g. `ObservePlaylists`), **Repos** own entity + DAO + cache. |
| 91 | +- **Cross-cutting UI state via Composition Locals**, provided in `DatmusicApp.kt`: `LocalPlaybackConnection`, `LocalDownloader`, `LocalAudioActionHandler`, `LocalAudioDownloadItemActionHandler`, `LocalSnackbarHostState`, `LocalAnalytics`. Inject with `LocalX.current` in composables. |
| 92 | +- **Navigation**: 4 root tabs (Search, Downloads, Library, Settings) each hosting a nav graph of leaf screens; bottom sheets registered via Accompanist navigator; deep links patterned as `https://datmusic.xyz/...` (artist/album/playlist/search). Add new screens in `navigation/screens/Screens.kt` and wire in `AppNavigation` (`app/.../ui/AppNavigation.kt`). |
| 93 | +- **Previews**: every screen composable has a `@CombinedPreview`-annotated preview backed by fakes in `core-ui/ui/previews/` (e.g. `PreviewDatmusicCore`, `PreviewPlaybackConnection`). Keep fakes consistent when adding previews. |
| 94 | +- **Theming**: `ThemeViewModel` state → `AppTheme(themeState)`; theme specs (padding, typography) in `common-ui-theme`. |
| 95 | + |
| 96 | +## Build, test, CI |
| 97 | + |
| 98 | +```bash |
| 99 | +./gradlew spotlessCheck # style gate (CI runs this) |
| 100 | +./gradlew spotlessApply # fix style |
| 101 | +./gradlew testDebug # unit tests (CI runs this) |
| 102 | +./gradlew test # all unit tests |
| 103 | +./gradlew assembleDebug # debug APK |
| 104 | +./gradlew assembleRelease # release APK (R8 + resource shrinking) |
| 105 | +./gradlew publishRelease --track=production # upload to Play (alpha|beta|production) |
| 106 | +./gradlew :modules:core-data:test --tests "*AudiosDaoTest*" # single test |
| 107 | +``` |
| 108 | + |
| 109 | +- CI (`.github/workflows/android.yml`, `android-pr.yml`): `spotlessCheck` + `testDebug` on push/PR, JDK 21, uploads JUnit reports. |
| 110 | +- Release builds also run on Bitrise (`bitrise.yml`); signing material in `signing/` (release keystore may be encrypted as `.aes`, see `signing/decrypt.sh`). |
| 111 | +- Play publish uses `com.github.triplet.play` with `signing/play-account.json` (env `PUBLISH_TRACK` overrides track, default `alpha`). |
| 112 | +- Release signing env vars: `DATMUSIC_RELEASE_KEYSTORE_PWD`, `DATMUSIC_RELEASE_KEY_PWD`, `DATMUSIC_RELEASE_KEY_ALIAS`. |
| 113 | +- Version bumps: edit `buildSrc/src/main/java/tm/alashow/buildSrc/App.kt` (versionCode + versionName). Debug builds get `-DEBUG` suffix and app id `.debug`. |
| 114 | + |
| 115 | +## Conventions & gotchas |
| 116 | + |
| 117 | +- **License header** required on every Kotlin file (template: `spotless/copyright.kt`) — Spotless enforces it. |
| 118 | +- ktlint: android style, **200 char max line**, 4-space indent; run `./gradlew spotlessApply` before committing. |
| 119 | +- **Strings**: add new user-facing strings to `modules/i18n/src/main/res/values/app_strings.xml` and translate in the other locale folders. Don't hardcode strings in composables. |
| 120 | +- **Versions**: everything is in `gradle/libs.versions.toml` (version catalog `libs`); reference as `libs.*`. Don't pin versions in module `build.gradle` files. |
| 121 | +- Repos are centralized in `settings.gradle` (`RepositoriesMode.FAIL_ON_PROJECT_REPOS`); note the androidx.dev + sonatype snapshot repos are there intentionally. |
| 122 | +- All `subprojects` exclude `appcompat` and Material (old) — Compose only; don't re-add view-based libs. |
| 123 | +- `app/build.gradle` uses `prop(...)` to read config from project properties **or env vars** (e.g. `PUBLISH_TRACK`). |
| 124 | +- Room migrations must be added explicitly in `AppDatabase` (`AutoMigration` specs live in `core-data/.../db/Migrations.kt`); DAO tests exist under `core-data/src/test/.../daos/` as reference. |
| 125 | +- The project tracks cutting-edge dependencies (Kotlin beta, Compose alpha, media3-ext snapshot). Bumps can break the build — prefer minimal version changes and verify with `spotlessCheck testDebug assembleDebug`. |
| 126 | +- `.idea/`, `.gradle/`, `modules/build/` are local/IDE noise; ignore unless asked. |
| 127 | +- Deep link / share URLs use `Config.BASE_URL` (https://datmusic.xyz) — keep them in sync with `Screens.kt` deep link patterns. |
| 128 | +- Firebase config: `app/google-services.json`; FCM token registration interactor is in `core-data` (`interactors/RegisterFcmToken.kt`). |
| 129 | +- Backup/restore (library JSON export) is a real feature: `core-library/.../interactors/backup/`, UI in `ui-settings/.../backup/`. |
| 130 | + |
| 131 | +## Quick orientation for common tasks |
| 132 | + |
| 133 | +| Task | Where to look | |
| 134 | +|---|---| |
| 135 | +| Add a screen | `navigation/screens/Screens.kt` → new `ui-*` module (or extend existing) → `app/.../ui/AppNavigation.kt` | |
| 136 | +| Add an API endpoint | `core-data/.../api/DatmusicEndpoints.kt` + models in `core-domain` | |
| 137 | +| Change DB schema | `core-data/.../db/AppDatabase.kt` + `Migrations.kt` + DAO tests | |
| 138 | +| Playback behavior | `core-playback` (player/service) + `core-ui-playback` (UI) | |
| 139 | +| Downloads | `core-downloader` + `ui-downloads` + `core-ui-downloader` | |
| 140 | +| Playlists | `core-library` + `ui-library` + `core-ui-library` | |
| 141 | +| Theme/colors/typography | `common-ui-theme` | |
| 142 | +| New user-facing text | `modules/i18n/src/main/res/values*/` | |
| 143 | +| Billing/premium | `base-android/.../billing/` + `ui-settings/.../premium/` + `app/src/main/play/subscriptions/` | |
| 144 | +| Release/version | `buildSrc/.../App.kt`, `app/build.gradle` play plugin, `signing/` | |
0 commit comments