{"id":"cmv06akyz001hpj2o93e4r5gu","slug":"app-ortu","judul":"App Ortu (Android)","deskripsi":"App Android ortu — Kotlin + Jetpack Compose, repo ZX-Parenting-ortu","parentId":"cmv05qyqx000htm4dwawyj1ll","urut":8,"kategori":"mobile","tag":"[\"kotlin\",\"compose\",\"android\",\"ortu\"]","status":"aktif","versi":"2.9.7","konsep":"## Tujuan\nApp ortu pantau anak — **simple**, **fast**, **reliable**.\n\n- Target: ortu Indonesia 25-45 thn\n- Core: tugas + reward + screen time\n- Differentiator: AI insights + gamifikasi","createdAt":"2026-10-08T23:33:53.963Z","updatedAt":"2026-10-10T03:57:08.953Z","bagian":[{"id":"cmv06al1p001jpj2ob27q9fal","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"Ringkasan","konten":"App Android untuk orang tua pantau anak. Kotlin + Jetpack Compose.\n\n**Repo:** github.com/djvpri/ZX-Parenting-ortu\n**Local:** `/opt/data/zx-ortu`\n**Branch:** `main`\n**Versi:** v2.9.7 (versiKode 35)\n**Package:** `com.zxparenting.ortu`\n**Min SDK:** 24 (Android 7.0+)\n**Target/Compile SDK:** 35 (Android 15)\n\n**Config:** `alamat.json` (versiNama, versiKode, idPaket, beranda, api, googleWebId)\n**API base:** `https://zxparenting.zomet.my.id/api/`\n\n**CI:**\n- `ci.yml` — auto-trigger push, `./gradlew assembleDebug` (compile check)\n- `rilis.yml` — manual `workflow_dispatch` input `versi`, build AAB + APK, tag, GitHub Release\n\n**Play Store:** in-app update (FLEXIBLE), Credential Manager (Google Sign-In)","media":"[]","urut":0,"createdAt":"2026-10-08T23:33:54.061Z","updatedAt":"2026-10-08T23:33:54.061Z"},{"id":"cmv06al49001lpj2oeyww1n3x","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"Arsitektur","konten":"**Pattern:** MVVM (Model-ViewModel-View) + Repository\n\n```\nMainActivity (ComponentActivity)\n├── TemaZX (Material3 wrapper)\n├── Simpanan (DataStore — token, nama, uid)\n├── ZxVm : ViewModel (state = MutableStateFlow<UiState>)\n│   ├── Klien.api : ApiZx (Retrofit2)\n│   └── muatSemua(token) — 8 parallel fetch\n└── Scaffold\n    ├── NavigationBar (5 tab)\n    └── Content: 5 tab + 14 sub-layar\n```\n\n**State:** single `UiState` data class, `MutableStateFlow`, `collectAsState()` di Compose.\n\n**Navigation:** tidak pakai Navigation Compose. Tab = `mutableIntStateOf`, subLayar = `mutableIntStateOf`. Beranda punya grid tile → klik set `subLayar` 1-13.\n\n**Coroutines:** `viewModelScope.launch` untuk semua API call. `Dispatchers.IO` implicit via Retrofit suspend.","media":"[]","urut":1,"createdAt":"2026-10-08T23:33:54.153Z","updatedAt":"2026-10-08T23:33:54.153Z"},{"id":"cmv06al6z001npj2olinp8s75","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"UI / Layar","konten":"**20 layar Composable** (`ui/layar/Layar*.kt`):\n\n**Auth (2):**\n- `LayarLogin` — email+password, Google Sign-In (Credential Manager)\n- `LayarDaftar` — nama, email, password, setuju consent\n\n**Bottom Tab (5):**\n- `LayarBeranda` (tab 0) — dashboard: nama, tier, saldo coin, quick tiles (14 menu), list anak+tugas\n- `LayarAnak` (tab 1) — list anak, form buat/edit, `DialogEditAnak`, `DialogResetPin` (v2.9.7)\n- `LayarTugas` (tab 2) — list tugas per anak, form buat, validasi (selesai/tolak)\n- `LayarMarketplace` (tab 3) — hadiah CRUD, pesanan proses (beli/tolak), saldo coin\n- `LayarProfil` (tab 4) — nama, logout, hapus akun\n\n**Sub-layar dari Beranda (14):**\n1. `LayarAturan` — device control: sessionLimit, cooldown, dailyLimit, curfew, appBlokir, masterUnlock\n2. `LayarLokasi` — GPS toggle, geo-fence (lat, lng, radius), lastSeen\n3. `LayarLaporan` — activity log per anak, ringkasan 7 hari\n4. `LayarCoin` — saldo ZX Coin, ledger, topup/penyesuaian\n5. `LayarQuest` — family quest list, buat quest\n6. `LayarForum` — forum posts, detail + komentar, buat/hapus\n7. `LayarLangganan` — tier (FREE_TRIAL/PRO/ELITE), hari sisa, checkout Midtrans\n8. `LayarPesan` — direct chat list + thread\n9. `LayarInsights` — AI insights per anak (Gemini/rule-based)\n10. `LayarPinjam` — pinjam waktu + hutang\n11. `LayarReferral` — kode referral, klaim\n12. `LayarLeaderboard` — ranking token mingguan, scope global\n13. `LayarChallenges` — daily challenges + claim\n14. *(slot 14 reserved)*","media":"[]","urut":2,"createdAt":"2026-10-08T23:33:54.251Z","updatedAt":"2026-10-08T23:33:54.251Z"},{"id":"cmv06al9u001ppj2oze5zjj9m","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"Data Layer","konten":"**3 komponen data:**\n\n**1. Simpanan** (`data/Simpanan.kt`) — DataStore Preferences\n- `token`, `nama`, `userId` (Flow<String?>)\n- `simpanSesi(token, nama, uid)` — save login\n- `hapus()` — clear (logout)\n- Store name: `zxortu`\n\n**2. Klien** (`api/Klien.kt`) — Retrofit2 singleton\n- Base URL: `https://zxparenting.zomet.my.id/api/`\n- OkHttpClient: HttpLoggingInterceptor (BODY level)\n- Converter: Gson\n- `val api: ApiZx by lazy { ... }`\n\n**3. ApiZx** (`api/Api.kt`) — Retrofit interface, ~40 endpoints\n- Auth: login, daftar, Google\n- Anak: list, create, detail, update, delete, resetPin\n- Tugas: list, create, validasi\n- Device: list, patch, delete\n- Coin: get, post (topup)\n- Hadiah: list, create, patch, delete, prosesPesanan\n- Quest: list, create\n- Langganan: status\n- Pesan: list, thread, kirim\n- Forum: list, create, detail, delete, komentar\n- Insights, Pinjam, Referral, Leaderboard, Challenges\n\n**Data classes:** ~40 `data class` (request/response models), Gson `@SerializedName` untuk snake_case","media":"[]","urut":3,"createdAt":"2026-10-08T23:33:54.354Z","updatedAt":"2026-10-08T23:33:54.354Z"},{"id":"cmv06aldx001rpj2ohdowtxqi","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"State Management","konten":"**UiState** — single data class, semua state UI di satu tempat:\n\n```kotlin\ndata class UiState(\n    loading, error, loginOk, nama, token,\n    devices: List<Device>,\n    anakList: List<Anak>,\n    tugasList: List<Tugas>,\n    aktivitas: AktivitasRes?,\n    market: HadiahMarketRes?,\n    coin: CoinRes?,\n    quests: List<Quest>,\n    forumPosts: List<ForumPost>,\n    forumDetail: ForumPost?,\n    langganan: LanggananRes?,\n    pesanList, pesanThread,\n    insights: InsightsRes?,\n    pinjam: PinjamRes?,\n    referral: ReferralRes?,\n    leaderboard: LeaderboardRes?,\n    challenges: ChallengesRes?,\n    claimResult: String?,\n)\n```\n\n**ZxVm** (`ui/ZxVm.kt`, 757 lines):\n- `val state = MutableStateFlow(UiState())`\n- `init {}` — cek DataStore token, auto-login jika ada\n- `suksesLogin(body)` — simpan sesi, `muatSemua(token)`\n- `muatSemua(token)` — 8 parallel fetch: devices, anak, tugas, market, coin, quest, forum, langganan\n- Sub-layar data lazy-load: `muatAktivitas`, `muatInsights`, `muatPinjam`, `muatChallenges`, `muatLeaderboard`, `muatThread`, `muatForumDetail`\n- `ZxVmFactory(simpanan)` — factory untuk ViewModel construction","media":"[]","urut":4,"createdAt":"2026-10-08T23:33:54.501Z","updatedAt":"2026-10-08T23:33:54.501Z"},{"id":"cmv06alil001tpj2o0woi411l","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"Theming & i18n","konten":"**Theme system** (`ui/tema/`):\n\n**Palet** (`Palet.kt`) — JSON-driven color palette\n- 16 colors: bg, kartu, utama, aksen, teks, teksMuted, muted, border, biru, ungu, amber, hijau, merah, pink, biruGelap\n- Default: \"Clay\" — biru utama `#2563EB`, bg `#EFF6FF`\n- `TemaRepo` — load dari `assets/tema/*.json`, swap runtime via `WarnaAktif.ganti(palet)`\n- `Palet.dariJson()` — parse JSON ke Palet\n\n**Tema** (`Tema.kt`):\n- `WarnaAktif` — singleton mutable palet, auto-recompose\n- `TemaZX` — Material3 wrapper, auto light/dark via luminansi\n- `Tipografi` — titleLarge 19sp Bold, titleMedium 15sp Bold, bodyMedium 13sp, bodySmall 11sp\n- Shape radius: besar 22dp, kartu 18dp, kecil 12dp\n\n**i18n** (`Teks.kt`):\n- `enum class Bahasa { ID, EN }`\n- `object Teks` — dictionary map, `setBahasa()`, operator `[]`\n- Keys: beranda, anak, tugas, aturan, profil, market, coin, quest, lokasi, laporan, forum, langganan, pesan, insights\n- ponytail: scaffold ID/EN. Upgrade: strings.xml + per-language resource","media":"[]","urut":5,"createdAt":"2026-10-08T23:33:54.669Z","updatedAt":"2026-10-08T23:33:54.669Z"},{"id":"cmv06alm4001vpj2o8a8afyw0","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"Build & CI","konten":"**build.gradle.kts** (`app/`):\n- `namespace = \"com.zxparenting.ortu\"`\n- `compileSdk = 35`, `minSdk = 24`, `targetSdk = 35`\n- `applicationId` = baca dari `alamat.json` (dynamic)\n- `versiNama`/`versiKode` = baca dari `alamat.json`\n- Java 17 (Temurin)\n- Google Sign-In: Credential Manager + credentials-play-services-auth\n\n**Version config:** `alamat.json`\n```json\n{\n  \"idPaket\": \"com.zxparenting.ortu\",\n  \"versiNama\": \"2.9.7\",\n  \"versiKode\": 35,\n  \"beranda\": \"https://zxparenting.zomet.my.id\",\n  \"api\": \"https://zxparenting.zomet.my.id/api\",\n  \"googleWebId\": \"117197293834-...\"\n}\n```\n\n**CI (`ci.yml`):** auto-trigger push → `assembleDebug` (compile check, no keystore)\n\n**Rilis (`rilis.yml`):** manual `workflow_dispatch` input `versi`:\n1. Bump `alamat.json` (versiNama + versiKode+1)\n2. Keystore: repo file (`keystore/rilis.keystore` + `.pw`) atau Secrets\n3. `bundleRelease assembleRelease` → AAB + APK\n4. Commit versi, tag `v{versi}`, GitHub Release\n\n**In-app update:** `AppUpdateManager` (FLEXIBLE type), dialog \"Update tersedia\"\n\n**Google Sign-In:** Credential Manager API, `GetGoogleIdOption` + fallback `GetSignInWithGoogleOption`","media":"[]","urut":6,"createdAt":"2026-10-08T23:33:54.797Z","updatedAt":"2026-10-08T23:33:54.797Z"},{"id":"cmv06aloq001xpj2o9li25kmf","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"Pitfall","konten":"⚠ `alamat.json` = source of truth versi. Bukan `build.gradle.kts`. CI rilis bump `versiKode+1` otomatis.\n\n⚠ Tidak pakai Navigation Compose. Tab = `mutableIntStateOf`, subLayar = `mutableIntStateOf`. Sederhana tapi tidak scalable.\n\n⚠ `UiState` single data class — semua state di satu object. Besar (20+ fields) tapi simple.\n\n⚠ `muatSemua()` launch 8 coroutine parallel tanpa join. Race condition tidak di-handle tapi praktis OK karena masing-maku update field beda.\n\n⚠ Tema via JSON `assets/tema/*.json`. Tambah tema = drop file JSON. Tidak download remote.\n\n⚠ i18n via `object Teks` in-memory map. Bukan `strings.xml`. Ponytail: upgrade saat multi-negara.\n\n⚠ Google Sign-In butuh app terinstall dari Play Store (Credential Manager constraint). Fallback: `GetSignInWithGoogleOption`.\n\n⚠ `GOOGLE_WEB_ID` hardcoded di `MainActivity.companion`. Juga di `alamat.json.googleWebId`.\n\n⚠ Verify via CI only — WSL Hermes tidak punya Java/Android SDK. Tidak bisa local build.\n\n⚠ Container Coolify ID berubah tiap deploy. App ortu tidak terdampak (pakai domain `zxparenting.zomet.my.id`).\n\n⚠ Reset PIN 3 iterasi bug fix — lihat child blueprint \"Auth Anak (PIN)\" bagian \"Reset PIN Flow\".","media":"[]","urut":7,"createdAt":"2026-10-08T23:33:54.891Z","updatedAt":"2026-10-09T14:54:07.902Z"},{"id":"cmv06alro001zpj2oljvpdq4z","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"Catatan Rilis App","konten":"**v2.9.7** — pisah `DialogResetPin` dari `DialogEditAnak` (3 iterasi fix reset PIN)\n**v2.9.6** — pesan error reset PIN langsung dari callback `(sukses, pesan)`\n**v2.9.5** — reset PIN async callback + loading spinner\n**v2.9.4** — edit username anak + `AnakUpdateReq.username` + `AnakResetPinReq` + `anakResetPin @PATCH`\n**v2.9.3** — `fmtTgl()` fix ISO 8601 → `yyyy-MM-dd` (substring 0,10)\n**v2.9.2** — DatePicker native + dropdown gender\n**v2.9.1** — edit + hapus anak (`PATCH/DELETE anak/{id}`), `tanggalLahir` ganti `umur`\n**v2.9.0** — referral + leaderboard + challenges + family quest\n**v2.8.0** — pinjam waktu + AI insights (Gemini)\n**v2.7.1** — i18n scaffold (Bahasa ID/EN)","media":"[]","urut":8,"createdAt":"2026-10-08T23:33:54.996Z","updatedAt":"2026-10-08T23:33:54.996Z"},{"id":"cmv2n8sf0000dmg2oiy20huo9","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"SCREENSHOOT","konten":"https://imgur.com/yGxZcSr\nhttps://imgur.com/631sZeT\nhttps://imgur.com/ynz4vy7\nhttps://imgur.com/S3OGvlg\nhttps://imgur.com/BN50YZ2","media":"[]","urut":9,"createdAt":"2026-10-10T17:03:56.124Z","updatedAt":"2026-10-10T17:04:51.127Z"},{"id":"cmv346je0000fmg2oznfmeu2v","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"ICON","konten":"https://zxparenting.zomet.my.id/design/icon-download.html","media":"[]","urut":10,"createdAt":"2026-10-11T00:58:04.584Z","updatedAt":"2026-10-11T00:58:09.512Z"},{"id":"cmv34f4o7000hmg2ovezx6adc","blueprintId":"cmv06akyz001hpj2o93e4r5gu","judul":"GAMBAR FITUR","konten":"","media":"[{\"id\":\"26eab9cf31ec3572\",\"tipe\":\"gambar\",\"url\":\"/api/uploads/bp/app-ortu/1791684835010-86c4c4d7.png\",\"label\":\"\"}]","urut":11,"createdAt":"2026-10-11T01:04:45.415Z","updatedAt":"2026-10-11T02:13:55.015Z"}],"children":[{"slug":"tugas-module","judul":"Tugas (Module)","status":"aktif"},{"slug":"laporan","judul":"Laporan","status":"draft"},{"slug":"menu-tugas","judul":"Menu Tugas","status":"aktif"}],"parent":{"slug":"zx-parenting","judul":"ZX Parenting"}}