diff --git a/docs/integration-mobile-customer.md b/docs/integration-mobile-customer.md new file mode 100644 index 0000000..07e2b77 --- /dev/null +++ b/docs/integration-mobile-customer.md @@ -0,0 +1,799 @@ +# Integrasi Mobile App Customer: EnakPoint, EnakCoin, EnakGame & Voucher + +**Untuk:** tim aplikasi mobile customer · **Base URL:** `/api/v1` · **Per:** 8 Okt 2026 + +Kamu mengerjakan aplikasi mobile untuk **customer** (bukan kasir, bukan backoffice). +Tugasmu: membangun fitur loyalitas di aplikasi, yaitu saldo EnakPoint & EnakCoin, +PIN, tukar, transfer, voucher, dan pintu masuk ke game EnakGame. Semuanya memakai API +backend yang sudah jadi dan dijelaskan di dokumen ini. Jangan mengarang endpoint, +field, atau aturan yang tidak tertulis di sini; kalau ada yang kurang jelas, tanyakan +dulu. + +Dokumen ini menggantikan `mobile-customer-enakpoint.md`, `integration-enakpoint.md`, +`api-enakpoint.md`, dan `enakgame-spin.md` untuk sisi aplikasi customer. Game-nya +sendiri (Phaser) dikerjakan tim EnakGame dengan +[`integration-enakgame.md`](./integration-enakgame.md). + +--- + +## 1. Konteks bisnis + +| | EnakPoint (`POINT`) | EnakCoin (`COIN`) | +|---|---|---| +| Didapat dari | Belanja (order lunas), tukar EnakCoin, koreksi admin | Belanja, **hadiah game**, koreksi admin | +| Dipakai untuk | **Ditukar ke voucher** (tidak bisa membayar order) | **Main game**, ditukar ke EnakPoint | +| Bisa dikirim ke customer lain | Ya | Ya | +| Bisa kedaluwarsa | Ya, bila owner mengaktifkan | Ya, bila owner mengaktifkan | + +Tidak ada lagi "token". Semua yang dulu token sekarang EnakCoin. + +### Aturan yang wajib dipatuhi di UI + +1. **Semua jumlah bilangan bulat.** Tidak ada desimal pada EnakPoint atau EnakCoin. +2. **Saldo bukan uang.** Nilai rupiah EnakPoint selalu ditulis **"setara potongan + Rp …"**, tidak pernah "saldo Rp …" atau "uang". Tidak ada tarik tunai, dan EnakPoint + tidak bisa dipakai membayar. Jangan membangun layar bayar atau kode bayar. +3. **PIN 6 digit wajib** untuk: tukar EnakCoin, transfer, dan **tukar EnakPoint ke + voucher**. Main game, melihat saldo, dan riwayat tidak butuh PIN. +4. **PIN terpisah dari password login** dan selalu dikirim sebagai **string** (supaya + nol di depan tidak hilang). Jangan pernah menyimpan PIN di perangkat, log, atau + analytics. +5. **Satu akun customer = satu organisasi.** Saldo berlaku di semua outlet organisasi itu. +6. **Waktu memakai WIB.** Tanggal kedaluwarsa berarti saldo masih bisa dipakai sampai + 23:59:59 WIB di tanggal itu. +7. **Hadiah game ditentukan server.** Aplikasi tidak menghitung atau mengirim hadiah. + +--- + +## 2. Koneksi ke API + +- Base URL: `/api/v1` +- Semua endpoint customer: header `Authorization: Bearer ` +- Semua jumlah di request dan response berupa integer. +- Belum ada endpoint refresh token: bila token ditolak (§2.2, `entity` `auth_handler`), + customer login ulang. + +### 2.1 Registrasi customer + +`POST /api/v1/customer-auth/register/start` menerima `organization_id` (opsional): + +```json +{ "phone_number": "0812…", "name": "Budi", "birth_date": "2000-01-31", "organization_id": "648b96a0-1d1d-414e-baee-37e9d6317b4e" } +``` + +- Customer terdaftar di satu organisasi, dan saldonya berlaku di semua outlet organisasi itu. +- Bila `organization_id` tidak dikirim dan backend hanya punya satu organisasi, customer + otomatis masuk ke organisasi itu. Bila ada lebih dari satu, registrasi ditolak + ("organization_id is required"), jadi sebaiknya app selalu mengirimnya dari config per + environment/brand. +- `organization_id` yang dikirim harus ada; bila tidak, registrasi ditolak sebelum OTP dikirim. +- Wallet customer baru belum punya baris sampai saldo pertama kali bergerak; + `GET /customer/wallet` tetap menjawab saldo 0. + +### 2.2 Format response + +Sukses: + +```json +{ "success": true, "data": { … }, "errors": null } +``` + +Semua contoh response di dokumen ini adalah isi `data`. Untuk daftar berhalaman, isi +itu sendiri berbentuk `{ "data": [ … ], "pagination": { … } }`, jadi array-nya ada di +`data.data` pada response mentah. + +Gagal: + +```json +{ "success": false, "data": null, "errors": [{ "code": "304", "entity": "wallet_service", "cause": "wallet move refused: not enough EnakCoin" }] } +``` + +| `errors[0].code` | HTTP | Arti | Yang dilakukan app | +|---|---|---|---| +| `303`, `310` | 400 | Request tidak lengkap / salah format | Bug di app; tampilkan pesan umum | +| `304` | 400 | Ditolak aturan bisnis, **atau token tidak berlaku** bila `entity` = `auth_handler` | Pesan yang ramah per fitur; `cause` berbahasa Inggris, jangan tampilkan mentah. Token: login ulang | +| `404` | 404 | Tidak ditemukan, juga untuk data milik customer lain | Tampilkan "tidak ditemukan" | +| `429` | 429 | Minta OTP terlalu cepat | Hitung mundur sebelum boleh minta lagi | +| `PIN_NOT_SET` | 403 | Belum punya PIN | Buka alur buat PIN (§6.2) | +| `PIN_INVALID` | 400 | PIN salah | §6.5 | +| `PIN_LOCKED` | 423 | PIN terkunci | §6.5 | +| `TRANSFER_BLOCKED` | 403 | Transfer ditahan setelah reset PIN | §6.5 | +| `900` | 500 | Error server | "Terjadi kesalahan, coba lagi" | + +### 2.3 Idempotency-Key + +Endpoint **tukar**, **transfer**, dan **tukar voucher** wajib header `Idempotency-Key` +(string unik, maks. 50 karakter, mis. UUID v4; `X-Idempotency-Key` juga diterima). + +- Buat **satu key baru saat customer menekan tombol konfirmasi**. +- Bila request gagal karena jaringan/timeout, **kirim ulang dengan key yang sama**. + Server mengembalikan hasil pertama dengan `"replayed": true` dan tidak memotong saldo + dua kali. +- Jangan pakai ulang key untuk transaksi yang berbeda; server menolaknya (`304`). + +--- + +## 3. Layar yang perlu dibuat + +| Layar | Endpoint utama | Butuh PIN | +|---|---|---| +| Beranda wallet | `GET /customer/wallet` | – | +| Riwayat mutasi | `GET /customer/wallet/transactions` | – | +| Saldo akan kedaluwarsa | `GET /customer/wallet/expiring` | – | +| Daftar outlet | `GET /customer/outlets` | – | +| Riwayat order + detail | `GET /customer/orders`, `GET /customer/orders/:id` | – | +| Tukar EnakCoin | `GET …/exchange/preview`, `POST /customer/wallet/exchange` | Ya | +| Transfer | `GET …/transfer/recipient`, `POST /customer/wallet/transfer` | Ya | +| PIN (buat, ganti, lupa) | `/customer/pin/*` | – | +| Daftar game + webview game | `GET /customer/enakgame/games` | – | +| Riwayat main | `GET /customer/enakgame/sessions` | – | +| Katalog voucher | `GET /customer/vouchers` | – | +| Tukar voucher | `POST /customer/vouchers/:id/redeem` | Ya | +| Voucher saya | `GET /customer/vouchers/redemptions` | – | +| (latar belakang) registrasi push | `PUT` / `DELETE /customer/devices` | – | + +--- + +## 4. Beranda wallet, riwayat, kedaluwarsa + +### 4.1 Beranda — `GET /customer/wallet` + +```json +{ + "point_balance": 12500, + "coin_balance": 8, + "point_value": 1, + "point_discount_value": 12500, + "nearest_expiring": { + "point": { "amount": 150, "date": "2026-12-31" }, + "coin": null + }, + "recent_transactions": [ /* sama dengan item riwayat §4.2, maksimal 5 */ ] +} +``` + +Tampilkan: +- Saldo EnakPoint (`point_balance`) dengan keterangan "setara potongan Rp + {point_discount_value}" (format ribuan Indonesia: `Rp 12.500`). +- Saldo EnakCoin (`coin_balance`). +- Bila `nearest_expiring.point` / `.coin` tidak `null`: banner "{amount} EnakPoint akan + kedaluwarsa pada {date}" yang membuka layar §4.3. +- 5 mutasi terakhir dari `recent_transactions`, dengan tautan "Lihat semua" ke §4.2. +- Tombol aksi: Tukar EnakCoin (§7.1), Transfer (§7.2), Main game (§8), Voucher (§9). + +Muat ulang beranda setelah setiap transaksi, saat webview game ditutup, dan saat +menerima push (§5). + +Field `total_points`, `points_history`, `last_updated` di response ini **deprecated**; +jangan dipakai. + +### 4.2 Riwayat — `GET /customer/wallet/transactions` + +Query (semua opsional): + +| Query | Contoh | Keterangan | +|---|---|---| +| `page` | `1` | Mulai dari 1 | +| `limit` | `20` | 1–100, default 20 | +| `currency` | `POINT` | `POINT` atau `COIN`; untuk tab EnakPoint / EnakCoin | +| `type` | `EARN,TRANSFER_IN` | Satu atau beberapa tipe dipisah koma, untuk filter | +| `from`, `to` | `2026-09-01` | Tanggal WIB, inklusif | + +```json +{ + "data": [ + { + "id": "…", + "currency": "POINT", + "type": "EARN", + "amount": 875, + "balance_after": 12500, + "description": "Belanja #ORD-0123 di Outlet Kemang", + "source": { "type": "ORDER", "id": "…" }, + "outlet_id": "…", + "group_id": null, + "expires_at": "2026-12-31T23:59:59+07:00", + "lots": [{ "amount": 875, "remaining": 875, "expires_at": "2026-12-31T23:59:59+07:00" }], + "created_at": "2026-09-30T12:01:00Z" + } + ], + "pagination": { "page": 1, "limit": 20, "total_count": 42, "total_pages": 3 } +} +``` + +Aturan tampilan: +- `amount` bertanda: positif tampil hijau dengan `+`, negatif merah dengan `−`. +- Penambahan membawa `source`, pengurangan membawa `destination`, keduanya `{ type, id }`. +- `description` sudah siap tampil (nama lawan transfer sudah disamarkan, nama game dan + voucher sudah tertulis). Tampilkan apa adanya. +- Mutasi masuk yang punya `expires_at` menampilkan "Berlaku sampai {tanggal}". +- Infinite scroll memakai `pagination.total_pages`. +- Riwayat tidak pernah berubah atau hilang; koreksi muncul sebagai baris baru. + +Label tipe: + +| `type` | Mata uang | Label | Arah | +|---|---|---|---| +| `EARN` | keduanya | Dari belanja | + | +| `EARN_REVERSAL` | keduanya | Dibatalkan (order di-void/refund) | − | +| `EXCHANGE_OUT` | EnakCoin | Ditukar ke EnakPoint | − | +| `EXCHANGE_IN` | EnakPoint | Hasil tukar EnakCoin | + | +| `TRANSFER_OUT` | keduanya | Transfer keluar | − | +| `TRANSFER_IN` | keduanya | Transfer masuk | + | +| `GAME_SPEND` | EnakCoin | Main game | − | +| `GAME_SPEND_REFUND` | EnakCoin | Biaya main dikembalikan | + | +| `GAME_REWARD` | EnakCoin | Hadiah game | + | +| `REWARD_REDEEM` | EnakPoint | Ditukar ke voucher | − | +| `REWARD_REDEEM_REFUND` | EnakPoint | Penukaran voucher dibatalkan | + | +| `EXPIRE` | keduanya | Kedaluwarsa | − | +| `ADJUSTMENT` | keduanya | Koreksi | + / − | +| `MIGRATION` | keduanya | Saldo awal | + | + +Tipe yang tidak dikenal (bila backend menambah tipe baru): tampilkan `description` dan +arah dari tanda `amount`, tanpa label. + +### 4.3 Akan kedaluwarsa — `GET /customer/wallet/expiring` + +```json +{ + "point": [ + { "amount": 150, "date": "2026-10-31" }, + { "amount": 200, "date": "2026-12-31" } + ], + "coin": [] +} +``` + +Daftar per tanggal, paling dekat di atas. Daftar kosong: tampilkan "Tidak ada saldo +yang akan kedaluwarsa". Saldo yang kedaluwarsa hangus tanpa kompensasi. + +### 4.4 Daftar outlet — `GET /customer/outlets` + +Outlet aktif di organisasi customer, tempat saldo EnakPoint & EnakCoin berlaku. Urut +berdasarkan nama. + +```json +[ + { + "id": "…", + "name": "Gokuna Kemang", + "address": "Jl. Kemang Raya 10", + "earns_points": true, + "earns_coins": false + } +] +``` + +- `address` bisa `null`. +- `earns_points` / `earns_coins`: belanja di outlet ini memberi EnakPoint / EnakCoin. +- Belum ada telepon, koordinat, atau jam buka; data itu belum disimpan di backend. + +### 4.5 Riwayat order — `GET /customer/orders` dan `GET /customer/orders/:id` + +Order milik customer yang login di semua outlet organisasinya, terbaru di atas. Order +hanya masuk ke sini bila kasir mengaitkannya ke customer. + +`GET /api/v1/customer/orders?page=1&limit=20` (`limit` 1–100, default 20): + +```json +{ + "data": [ + { + "id": "…", + "order_number": "ORD-0123", + "outlet_id": "…", + "outlet_name": "Gokuna 1", + "order_type": "dine_in", + "status": "completed", + "payment_status": "completed", + "total_amount": 99000, + "item_count": 2, + "is_void": false, + "is_refund": false, + "points_earned": 865, + "coins_earned": 3, + "created_at": "2026-09-30T12:01:00Z" + } + ], + "pagination": { "page": 1, "limit": 20, "total_count": 42, "total_pages": 3 } +} +``` + +`GET /api/v1/customer/orders/{id}` mengembalikan field yang sama, ditambah: + +```json +{ + "table_number": "A3", + "subtotal": 90000, + "discount_amount": 0, + "tax_amount": 9000, + "refund_amount": 0, + "items": [ + { + "id": "…", + "product_id": "…", + "product_name": "Kopi Susu", + "variant_name": "Large", + "quantity": 2, + "unit_price": 25000, + "total_price": 50000, + "refund_quantity": 0, + "modifiers": [], + "status": "completed" + }, + { + "id": "…", + "product_id": "…", + "product_name": "Ikan Tude", + "variant_name": null, + "quantity": 1, + "weight": 4.2, + "unit_name": "ons", + "unit_price": 4500, + "total_price": 18900, + "refund_quantity": 0, + "modifiers": [], + "status": "completed" + } + ], + "payments": [ + { "id": "…", "method_name": "Cash", "method_type": "cash", "amount": 99000, "status": "completed", "refund_amount": 0, "created_at": "…" } + ] +} +``` + +- Order customer lain atau yang tidak ada → `404`. +- `points_earned` / `coins_earned`: yang didapat dari order ini; 0 bila tidak ada. +- Item timbangan membawa `weight` dan `unit_name`; tampilkan "1 × 4,2 ons". +- Order yang `is_void` atau `is_refund` tetap tampil, beri label "Dibatalkan" / + "Direfund". + +--- + +## 5. Notifikasi push (FCM) + +### 5.1 Registrasi device + +Setelah login berhasil **dan** setiap kali FCM memberi token baru (`onTokenRefresh`): + +`PUT /api/v1/customer/devices` + +```json +{ "device_id": "", "fcm_token": "", "platform": "android", "app_version": "2.4.0" } +``` + +- `device_id` wajib, stabil untuk satu instalasi (simpan di secure storage). +- `platform`: `android`, `ios`, atau `web`. +- Satu token FCM hanya milik satu customer: bila customer lain login di HP yang sama, + customer sebelumnya tidak lagi menerima notifikasi di HP itu. +- Saat **logout**, panggil `DELETE /api/v1/customer/devices/{device_id}` sebelum + menghapus token login. + +Tanpa registrasi ini, customer tidak menerima push apa pun. + +### 5.2 Tipe push + +Semua nilai di `data` berupa string. + +| `data.type` | Kapan | Isi `data` lain | Aksi saat di-tap | +|---|---|---|---| +| `WALLET_TRANSFER_IN` | Menerima transfer | `transaction_id`, `group_id`, `currency`, `amount` | Buka riwayat, sorot transaksi itu | +| `WALLET_EXPIRING` | `reminder_days` hari sebelum saldo hangus | `currency`, `amount`, `expiry_date` | Buka layar kedaluwarsa (§4.3) | +| `WALLET_EXPIRED` | Saldo baru saja hangus | `currency`, `amount` | Buka riwayat | +| `PIN_LOCKED` | PIN terkunci setelah 5 kali salah | `locked_until` (RFC3339 UTC) | Buka layar lupa PIN (§6.4) | + +Saat app terbuka dan menerima push wallet, muat ulang beranda. + +--- + +## 6. PIN + +### 6.1 Kapan diminta + +Jangan minta PIN saat registrasi. Minta saat customer **pertama kali** melakukan aksi +yang butuh PIN (tukar, transfer, tukar voucher). Cek dengan: + +`GET /api/v1/customer/pin/status` → `{ "has_pin": false, "locked_until": null, "transfer_blocked_until": null }` + +Bila `has_pin: false`, arahkan ke alur buat PIN, lalu kembali ke aksi semula. + +### 6.2 Buat PIN + +1. `POST /api/v1/customer/pin/otp` dengan `{ "purpose": "pin_setup" }`. + Response: `{ "purpose": "pin_setup", "otp_token": "…", "expires_at": "…" }`. + OTP dikirim ke WhatsApp customer. +2. Customer memasukkan kode OTP, lalu PIN dua kali. +3. `POST /api/v1/customer/pin` dengan + `{ "otp_token": "…", "otp_code": "123456", "pin": "482913", "confirm_pin": "482913" }`. + Response: status PIN. + +Validasi di app sebelum kirim (server juga memeriksa, jawab `304`): +- Tepat 6 digit angka, dan konfirmasi sama. +- Bukan satu digit berulang (`111111`). +- Bukan berurutan naik/turun (`123456`, `654321`). +- Bukan tanggal lahir customer (`DDMMYY` atau `YYMMDD`). + +Minta OTP lagi terlalu cepat → `429`: tampilkan hitung mundur. + +### 6.3 Ganti PIN + +`PUT /api/v1/customer/pin` dengan `{ "old_pin": "…", "pin": "…", "confirm_pin": "…" }`. + +### 6.4 Lupa PIN + +1. `POST /customer/pin/otp` dengan `{ "purpose": "pin_reset" }`. +2. `POST /customer/pin/reset` dengan `{ "otp_token", "otp_code", "pin", "confirm_pin" }`. + +Reset juga membuka PIN yang terkunci. Setelah reset, **transfer keluar ditahan 24 jam**; +tukar EnakCoin dan tukar voucher tetap bisa. Beri tahu customer hal ini di layar sukses. + +### 6.5 Menangani error PIN + +Semua endpoint yang menerima `pin` bisa menjawab error PIN. Pada error ini **`data` +tidak `null`**: + +```json +{ "success": false, "data": { "code": "PIN_INVALID", "remaining_attempts": 3 }, "errors": [ … ] } +``` + +| `data.code` | Field tambahan | Tampilan | +|---|---|---| +| `PIN_NOT_SET` | – | Buka alur buat PIN (§6.2) | +| `PIN_INVALID` | `remaining_attempts` | "PIN salah, sisa {n} percobaan." Kosongkan input PIN | +| `PIN_LOCKED` | `locked_until` | "PIN terkunci sampai {jam}." Tombol "Lupa PIN" | +| `TRANSFER_BLOCKED` | `transfer_blocked_until` | "Transfer bisa dilakukan lagi pada {waktu}." | + +5 kali salah berturut-turut mengunci PIN 30 menit; selama terkunci PIN yang benar pun +ditolak. Penghitung ada di server, jadi jangan membuat penghitung sendiri di app. + +--- + +## 7. Tukar dan transfer + +### 7.1 Tukar EnakCoin → EnakPoint + +1. Customer mengetik jumlah EnakCoin. Panggil preview (debounce saat mengetik): + + `GET /api/v1/customer/wallet/exchange/preview?coins=30` + + ```json + { "coin_amount": 10, "point_amount": 3, "coin_balance": 35, "coins": 30, "points": 9, "valid": true } + ``` + + - Kurs: `coin_amount` EnakCoin = `point_amount` EnakPoint. Tampilkan "10 EnakCoin = + 3 EnakPoint". + - Bila `valid: false`, tampilkan `reason` sebagai alasan dan nonaktifkan tombol. Jumlah + harus kelipatan `coin_amount`. + - Tampilkan "Kamu akan mendapat {points} EnakPoint". + +2. Konfirmasi (tukar tidak bisa dibatalkan) → minta PIN → + + `POST /api/v1/customer/wallet/exchange` + header `Idempotency-Key` + + ```json + { "coins": 30, "pin": "482913" } + ``` + + ```json + { + "group_id": "…", + "coins": 30, + "points": 9, + "coin_amount": 10, + "point_amount": 3, + "lots": [{ "amount": 9, "expires_at": "2026-12-31T23:59:59+07:00" }], + "coin_balance": 5, + "point_balance": 9, + "replayed": false + } + ``` + +3. Layar sukses: saldo baru, dan bila `lots[].expires_at` ada, "EnakPoint ini berlaku + sampai {tanggal}". EnakPoint hasil tukar tidak bisa hidup lebih lama dari EnakCoin + asalnya. + +Jumlah yang salah ditolak sebelum PIN dicek, jadi tidak memakan jatah percobaan PIN. + +### 7.2 Transfer + +1. Pilih mata uang (EnakPoint / EnakCoin), isi nomor HP penerima dan jumlah. +2. Cek penerima: + + `GET /api/v1/customer/wallet/transfer/recipient?phone=081234561234` + + ```json + { "name": "Bu*** Sa***", "phone_number": "08**-****-1234" } + ``` + + | Hasil | Tampilan | + |---|---| + | Sukses | "Kirim ke Bu*** Sa*** (08**-****-1234)?" | + | `404` | "Nomor ini tidak terdaftar" | + | `304` | "Tidak bisa mengirim ke nomor ini" (diri sendiri, akun nonaktif) | + +3. Konfirmasi (transfer final, tidak bisa dibatalkan) → minta PIN → + + `POST /api/v1/customer/wallet/transfer` + header `Idempotency-Key` + + ```json + { "currency": "POINT", "amount": 120, "recipient_phone": "081234561234", "pin": "482913" } + ``` + + ```json + { + "group_id": "…", + "currency": "POINT", + "amount": 120, + "recipient": { "name": "Bu*** Sa***", "phone_number": "08**-****-1234" }, + "lots": [ + { "amount": 100, "expires_at": "2026-12-31T23:59:59+07:00" }, + { "amount": 20, "expires_at": null } + ], + "balance": 30, + "replayed": false + } + ``` + +4. Layar sukses: saldo tersisa (`balance`). Bila ada `lots[].expires_at`, tampilkan + "Saldo yang dikirim berlaku sampai {tanggal}" (tanggal kedaluwarsa ikut terbawa ke + penerima). + +Penolakan `304` yang mungkin: transfer dimatikan owner, di bawah minimal, di atas +maksimal per transaksi, melewati batas harian (reset tengah malam WIB), saldo tidak +cukup. Tampilkan pesan umum "Transfer tidak bisa diproses" plus alasan yang sesuai +bila bisa dikenali. Bila kena `TRANSFER_BLOCKED`, ikuti §6.5. + +Penerima mendapat push `WALLET_TRANSFER_IN`. + +--- + +## 8. Game (EnakGame) + +Game dimainkan di **webview** yang memuat `game_url` tiap game. Pembagian tugasnya: +aplikasi menampilkan daftar game, membuka webview, dan memberi token lewat bridge; game +EnakGame sendiri yang memulai session, memotong EnakCoin, mengirim hasil, dan +menampilkan hadiah ([`integration-enakgame.md`](./integration-enakgame.md)). Aplikasi +**tidak** memanggil `POST /customer/enakgame/sessions` atau `…/complete`. + +### 8.1 Daftar game — `GET /customer/enakgame/games` + +```json +[ + { + "id": "8a1f…", + "slug": "spin", + "name": "Spin Harian", + "description": null, + "thumbnail_url": "https://…/spin.png", + "game_url": "https://…/spin/index.html", + "version": "1.2.0", + "entry_cost": 5, + "session_ttl_seconds": 600, + "events": [ + { "id": "…", "name": "Ramadan 2x", "banner_url": "https://…", "multiplier": 2, "bonus": null, "end_at": "2026-10-31T16:59:59Z" } + ], + "prizes": [ { "entry": 1, "label": "Zonk", "amount": 0 } ] + } +] +``` + +Tampilkan: +- Kartu per game: `thumbnail_url`, `name`, biaya "{entry_cost} EnakCoin". +- Badge event bila `events` tidak kosong: `name` atau `banner_url`, dan "berakhir + {end_at}" (tampilkan dalam WIB). +- Tombol Main nonaktif dengan teks "EnakCoin kurang" bila `coin_balance` (§4.1) lebih + kecil dari `entry_cost`. +- `prizes` hanya dipakai game spin di dalam webview; aplikasi boleh mengabaikannya. + +Game yang dinonaktifkan admin hilang dari daftar ini. Muat ulang daftar setiap kali +layar dibuka. + +### 8.2 Membuka game + +1. Customer menekan Main → buka webview layar penuh dengan `game_url`. +2. Pasang bridge (§8.3) **sebelum** halaman dimuat. +3. Saat game mengirim `ready`, jawab dengan `init`. +4. Saat game mengirim `close`, tutup webview, lalu muat ulang beranda wallet (§4.1). + +Jangan menaruh token di URL `game_url` (query string atau fragment): URL bisa tercatat di +log server game dan riwayat webview. + +### 8.3 Bridge (sisi aplikasi) + +> **Usulan.** Kontrak ini sama dengan [`integration-enakgame.md`](./integration-enakgame.md) +> §2 dan belum diimplementasikan. Sepakati dengan tim EnakGame sebelum mulai. + +- Game → aplikasi: JavaScript channel webview bernama **`EnakGameHost`**; setiap pesan + berupa JSON string. +- Aplikasi → game: jalankan `window.enakGame.receive('')` di webview. + +| Pesan masuk dari game | Yang dilakukan aplikasi | +|---|---| +| `{ "type": "ready" }` | Kirim `{ "type": "init", "api_base_url": "/api/v1", "token": "", "game_id": "" }` | +| `{ "type": "token_expired" }` | Login ulang customer (tidak ada refresh token), lalu kirim `{ "type": "token", "token": "" }` | +| `{ "type": "balance_changed", "coin_balance": 15 }` | Perbarui saldo EnakCoin yang ditampilkan aplikasi | +| `{ "type": "close" }` | Tutup webview, muat ulang beranda | + +Abaikan pesan dengan `type` lain. Tombol back Android jangan langsung menutup webview: +tampilkan konfirmasi "Keluar dari game? EnakCoin yang sudah dipakai untuk main tidak +kembali", lalu tutup. Tidak perlu mengirim pesan ke game. + +### 8.4 Riwayat main — `GET /customer/enakgame/sessions?page=1&limit=20` + +Query opsional `game_id` (riwayat satu game) dan `status` (`STARTED`, `COMPLETED`, +`REFUNDED`, `EXPIRED`) untuk filter atau tab. + +```json +{ + "data": [ + { + "id": "…", "game_id": "8a1f…", "status": "COMPLETED", "entry_cost": 5, "reward_total": 10, + "started_at": "…", "expires_at": "…", "ended_at": "…", "refund_reason": null + } + ], + "pagination": { "page": 1, "limit": 20, "total_count": 3, "total_pages": 1 } +} +``` + +| `status` | Label | Keterangan | +|---|---|---| +| `STARTED` | Sedang dimainkan | | +| `COMPLETED` | Selesai | "Dapat {reward_total} EnakCoin" | +| `REFUNDED` | Dikembalikan | Entry cost kembali; `refund_reason` `SYSTEM_ERROR` atau `GAME_DEACTIVATED` | +| `EXPIRED` | Tidak selesai | Hasil tidak dikirim sebelum batas waktu; entry cost tidak kembali | + +Nama game diambil dari daftar game (§8.1) lewat `game_id`. Detail satu session: +`GET /customer/enakgame/sessions/:id`. + +--- + +## 9. Voucher (tukar EnakPoint) + +### 9.1 Katalog — `GET /customer/vouchers` + +Voucher yang bisa ditukar sekarang: aktif, dalam masa berlaku, dan masih ada stoknya. + +```json +[ + { + "id": "…", + "name": "Kopi Susu Gratis", + "description": "Berlaku untuk ukuran regular", + "image_url": "https://…/kopi.png", + "voucher_type": "FREE_ITEM", + "face_value": 20000, + "point_cost": 15000, + "max_per_customer": 2, + "valid_until": "2026-12-31T16:59:59Z", + "terms": { "…": "syarat & ketentuan, objek JSON bebas" }, + "available": 120 + } +] +``` + +- `point_cost`: EnakPoint yang dipotong. Tombol Tukar nonaktif bila `point_balance` + kurang. +- `face_value`: nilai voucher dalam rupiah, tampilkan sebagai "senilai Rp 20.000". +- `available`: sisa stok; `null` berarti stok tidak dihitung. Bila 0, tampilkan "Habis". +- `max_per_customer`: batas tukar per customer; `null` = tanpa batas. +- `terms`: objek JSON yang isinya diatur admin. Sepakati bentuknya dengan tim + backoffice; sebelum itu tampilkan `description` saja. + +| `voucher_type` | Label usulan | +|---|---| +| `FIXED_VALUE` | Potongan Rp {face_value} | +| `PERCENTAGE` | Potongan persen | +| `FREE_ITEM` | Gratis item | +| `MERCHANT_BENEFIT` | Benefit merchant | + +### 9.2 Tukar — `POST /customer/vouchers/:id/redeem` + +Konfirmasi ("Tukar {point_cost} EnakPoint dengan {name}? Tidak bisa dibatalkan.") → +minta PIN → kirim dengan header `Idempotency-Key`: + +```json +{ "pin": "482913" } +``` + +```json +{ + "id": "…", + "voucher_id": "…", + "voucher_name": "Kopi Susu Gratis", + "voucher_image_url": "https://…/kopi.png", + "voucher_type": "FREE_ITEM", + "status": "COMPLETED", + "face_value": 20000, + "point_cost": 15000, + "code": "KOPI-7F3C-2291", + "code_expires_at": "2026-12-31T16:59:59Z", + "completed_at": "…", + "created_at": "…", + "point_balance": 2500, + "replayed": false +} +``` + +- Layar sukses: voucher, `code` bila ada (bisa disalin), masa berlaku, dan saldo + EnakPoint baru (`point_balance`). +- `code` bisa `null`: voucher ini tidak memakai kode; tunjukkan layar voucher ke kasir. +- `status: "PENDING"`: voucher sedang diproses penyedia luar (belum ada voucher seperti + ini di katalog, tapi tangani dari sekarang). EnakPoint sudah terpotong; + tampilkan "Voucher sedang diproses" dan cek lagi di Voucher saya (§9.3). Bila akhirnya + `FAILED`, EnakPoint dikembalikan otomatis (mutasi `REWARD_REDEEM_REFUND`). +- Error PIN ditangani sesuai §6.5. + +| Penolakan `304` (`cause`) | Tampilan | +|---|---| +| `not enough EnakPoint` | "EnakPoint kamu kurang." | +| `the voucher is out of stock` | "Voucher sudah habis." Muat ulang katalog | +| `this voucher can be redeemed at most … times per customer` | "Kamu sudah mencapai batas penukaran voucher ini." | +| `the voucher is not available`, `… cannot be redeemed yet`, `… has ended`, `… not available yet` | "Voucher tidak tersedia." Muat ulang katalog | +| `the customer is not active` | "Akun tidak aktif." | +| `this Idempotency-Key was already used to redeem another voucher` | Bug di app: key dipakai ulang | + +### 9.3 Voucher saya — `GET /customer/vouchers/redemptions?page=1&limit=20` + +Daftar penukaran customer, terbaru di atas, dengan bentuk item sama seperti response +§9.2 (tanpa `point_balance` dan `replayed`). Seperti semua daftar berhalaman di dokumen +ini, array-nya ada di `data.data` dan pagination di `data.pagination` di dalam amplop +response (§2.2); daftar kosong berupa `[]`. + +| `status` | Tampilan | +|---|---| +| `COMPLETED` | Voucher siap dipakai: nama, `code` (bila ada), berlaku sampai `code_expires_at` | +| `PENDING` | "Sedang diproses" | +| `FAILED` | "Gagal, EnakPoint sudah dikembalikan" | + +**Memakai voucher di outlet:** customer menunjukkan layar voucher ke kasir. POS belum +bisa menandai voucher terpakai, jadi aplikasi belum bisa menampilkan status "sudah +dipakai" ([`integration-pos.md`](./integration-pos.md) §5). + +--- + +## 10. Yang sudah dihapus / deprecated + +Sudah **dihapus** dari API (jangan dipanggil, akan error / tidak ada): + +| Lama | Pengganti | +|---|---| +| `POST /customer/spin` | Game EnakGame di webview (§8) | +| `GET /customer/games`, `GET /customer/ferris-wheel` | `GET /customer/enakgame/games` | +| `coins_used`, `coins_remaining`, `prize_won`, `game_play` di response spin | Tidak ada; hasil game ditampilkan di dalam game | +| `metadata.coin_cost` pada data game | `entry_cost` | +| `GET /customer/tokens` | `GET /customer/wallet` → `coin_balance` | +| `total_tokens`, `tokens_history`, `token_used`, `tokens_remaining` | `coin_balance`, `GET /customer/wallet/transactions?currency=COIN` | +| `POST /customer/wallet/payment-code` | Tidak ada; EnakPoint tidak bisa untuk bayar | +| `POST /customer/orders/:id/pay-with-points` | Tidak ada; EnakPoint tidak bisa untuk bayar | +| `accepts_point_payment` di `GET /customer/outlets` | – | +| `points_used`, `point_value` di `payments` pada `GET /customer/orders/:id` | – | +| Tipe mutasi `PAYMENT`, `PAYMENT_REFUND` di riwayat | Tidak ditulis lagi | + +Masih ada tapi **deprecated** (akan dihapus, jangan dipakai di kode baru): + +| Lama | Pengganti | +|---|---| +| `GET /customer/points` | `GET /customer/wallet` → `point_balance` | +| `total_points`, `points_history`, `last_updated` di `/customer/wallet` | `point_balance`, `recent_transactions` | + +--- + +## 11. Checklist selesai + +- [ ] Beranda menampilkan saldo EnakPoint ("setara potongan Rp …"), EnakCoin, dan banner kedaluwarsa terdekat. +- [ ] Riwayat dengan tab per mata uang, filter tipe/tanggal, infinite scroll, dan label semua tipe di §4.2, termasuk tipe game dan voucher. +- [ ] Layar saldo akan kedaluwarsa. +- [ ] Registrasi device FCM setelah login dan saat token berganti; unregister saat logout. +- [ ] Penanganan tap untuk keempat tipe push. +- [ ] PIN diminta hanya saat aksi yang membutuhkan; alur buat, ganti, dan lupa PIN lewat OTP. +- [ ] Keempat error PIN ditangani di semua layar yang meminta PIN (tukar, transfer, voucher). +- [ ] Tukar dengan preview, kelipatan kurs, konfirmasi, `Idempotency-Key`, retry dengan key sama. +- [ ] Transfer dengan cek penerima tersamar, konfirmasi, `Idempotency-Key`, retry dengan key sama. +- [ ] Daftar game dengan biaya, badge event, dan tombol nonaktif bila EnakCoin kurang. +- [ ] Webview game dengan bridge §8.3; token tidak pernah di URL; beranda dimuat ulang saat game ditutup. +- [ ] Riwayat main dengan label status. +- [ ] Katalog voucher, tukar dengan PIN dan `Idempotency-Key`, status `PENDING` ditangani. +- [ ] Voucher saya dengan kode yang bisa disalin. +- [ ] Riwayat order dengan pagination dan layar detail (item, pembayaran, EnakPoint/EnakCoin yang didapat). +- [ ] Tidak ada pemakaian endpoint atau field di §10. +- [ ] PIN dan token tidak pernah disimpan sembarangan, di-log, atau dikirim ke analytics. diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart deleted file mode 100644 index 3cf713f..0000000 --- a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:dartz/dartz.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:injectable/injectable.dart'; - -import '../../../domain/game/game.dart'; - -part 'ferris_wheel_loader_event.dart'; -part 'ferris_wheel_loader_state.dart'; -part 'ferris_wheel_loader_bloc.freezed.dart'; - -@injectable -class FerrisWheelLoaderBloc - extends Bloc { - final IGameRepository _repository; - FerrisWheelLoaderBloc(this._repository) - : super(FerrisWheelLoaderState.initial()) { - on(_onFerrisWheelLoaderEvent); - } - - Future _onFerrisWheelLoaderEvent( - FerrisWheelLoaderEvent event, - Emitter emit, - ) { - return event.map( - fetched: (e) async { - emit( - state.copyWith(isFetching: true, failureOptionFerrisWheel: none()), - ); - - final result = await _repository.ferrisWheel(); - - var data = result.fold( - (f) => state.copyWith(failureOptionFerrisWheel: optionOf(f)), - (ferrisWheel) => state.copyWith(ferrisWheel: ferrisWheel), - ); - - emit(data.copyWith(isFetching: false)); - }, - ); - } -} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart deleted file mode 100644 index ba96196..0000000 --- a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_event.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of 'ferris_wheel_loader_bloc.dart'; - -@freezed -abstract class FerrisWheelLoaderEvent with _$FerrisWheelLoaderEvent { - const factory FerrisWheelLoaderEvent.fetched() = _Fetched; -} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart b/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart deleted file mode 100644 index 49e1a55..0000000 --- a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_state.dart +++ /dev/null @@ -1,15 +0,0 @@ -part of 'ferris_wheel_loader_bloc.dart'; - -@freezed -abstract class FerrisWheelLoaderState with _$FerrisWheelLoaderState { - const factory FerrisWheelLoaderState({ - required Game ferrisWheel, - required Option failureOptionFerrisWheel, - @Default(false) bool isFetching, - }) = _FerrisWheelLoaderState; - - factory FerrisWheelLoaderState.initial() => FerrisWheelLoaderState( - ferrisWheel: Game.empty(), - failureOptionFerrisWheel: none(), - ); -} diff --git a/lib/application/game/game_spin/game_spin_bloc.dart b/lib/application/game/game_spin/game_spin_bloc.dart deleted file mode 100644 index 166d23a..0000000 --- a/lib/application/game/game_spin/game_spin_bloc.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:dartz/dartz.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:injectable/injectable.dart'; - -import '../../../domain/game/game.dart'; - -part 'game_spin_event.dart'; -part 'game_spin_state.dart'; -part 'game_spin_bloc.freezed.dart'; - -@injectable -class GameSpinBloc extends Bloc { - final IGameRepository _repository; - - GameSpinBloc(this._repository) : super(GameSpinState.initial()) { - on(_onGameSpinEvent); - } - - Future _onGameSpinEvent( - GameSpinEvent event, - Emitter emit, - ) { - return event.map( - spun: (e) async { - if (state.isSpinning) return; - emit(state.copyWith(isSpinning: true, failureOrResultOption: none())); - - final result = await _repository.spin(gameId: e.gameId); - - emit( - state.copyWith( - isSpinning: false, - failureOrResultOption: optionOf(result), - ), - ); - }, - ); - } -} diff --git a/lib/application/game/game_spin/game_spin_bloc.freezed.dart b/lib/application/game/game_spin/game_spin_bloc.freezed.dart deleted file mode 100644 index ba46c5a..0000000 --- a/lib/application/game/game_spin/game_spin_bloc.freezed.dart +++ /dev/null @@ -1,546 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'game_spin_bloc.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// GENERATED CODE - DO NOT MODIFY BY HAND -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$GameSpinEvent { - - String get gameId; -/// Create a copy of GameSpinEvent -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$GameSpinEventCopyWith get copyWith => _$GameSpinEventCopyWithImpl(this as GameSpinEvent, _$identity); - - - -@override -bool operator ==(Object other) { - final _this = this as GameSpinEvent; - return identical(this, other) || (other.runtimeType == runtimeType&&other is GameSpinEvent&&(identical(other.gameId, _this.gameId) || other.gameId == _this.gameId)); -} - - -@override -int get hashCode { - final _this = this as GameSpinEvent; - return Object.hash(runtimeType,_this.gameId); -} - -@override -String toString() { - final _this = this as GameSpinEvent; - return 'GameSpinEvent(gameId: ${_this.gameId})'; -} - - -} - -/// @nodoc -abstract mixin class $GameSpinEventCopyWith<$Res> { - factory $GameSpinEventCopyWith(GameSpinEvent value, $Res Function(GameSpinEvent) _then) = _$GameSpinEventCopyWithImpl; -@useResult -$Res call({ - String gameId -}); - - - - -} -/// @nodoc -class _$GameSpinEventCopyWithImpl<$Res> - implements $GameSpinEventCopyWith<$Res> { - _$GameSpinEventCopyWithImpl(this._self, this._then); - - final GameSpinEvent _self; - final $Res Function(GameSpinEvent) _then; - -/// Create a copy of GameSpinEvent -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? gameId = null,}) { - return _then(GameSpinEvent.spun( -null == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String, - )); -} - -} - - -/// Adds pattern-matching-related methods to [GameSpinEvent]. -extension GameSpinEventPatterns on GameSpinEvent { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap({TResult Function( _Spun value)? spun,required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _Spun() when spun != null: -return spun(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map({required TResult Function( _Spun value) spun,}){ -final _that = this; -switch (_that) { -case _Spun(): -return spun(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull({TResult? Function( _Spun value)? spun,}){ -final _that = this; -switch (_that) { -case _Spun() when spun != null: -return spun(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen({TResult Function( String gameId)? spun,required TResult orElse(),}) {final _that = this; -switch (_that) { -case _Spun() when spun != null: -return spun(_that.gameId);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when({required TResult Function( String gameId) spun,}) {final _that = this; -switch (_that) { -case _Spun(): -return spun(_that.gameId);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull({TResult? Function( String gameId)? spun,}) {final _that = this; -switch (_that) { -case _Spun() when spun != null: -return spun(_that.gameId);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _Spun implements GameSpinEvent { - const _Spun(this.gameId); - - -@override final String gameId; - -/// Create a copy of GameSpinEvent -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$SpunCopyWith<_Spun> get copyWith => __$SpunCopyWithImpl<_Spun>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spun&&(identical(other.gameId, gameId) || other.gameId == gameId)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,gameId); -} - -@override -String toString() { - return 'GameSpinEvent.spun(gameId: $gameId)'; -} - - -} - -/// @nodoc -abstract mixin class _$SpunCopyWith<$Res> implements $GameSpinEventCopyWith<$Res> { - factory _$SpunCopyWith(_Spun value, $Res Function(_Spun) _then) = __$SpunCopyWithImpl; -@override @useResult -$Res call({ - String gameId -}); - - - - -} -/// @nodoc -class __$SpunCopyWithImpl<$Res> - implements _$SpunCopyWith<$Res> { - __$SpunCopyWithImpl(this._self, this._then); - - final _Spun _self; - final $Res Function(_Spun) _then; - -/// Create a copy of GameSpinEvent -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? gameId = null,}) { - return _then(_Spun( -null == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String, - )); -} - - -} - -/// @nodoc -mixin _$GameSpinState { - - bool get isSpinning; Option> get failureOrResultOption; -/// Create a copy of GameSpinState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$GameSpinStateCopyWith get copyWith => _$GameSpinStateCopyWithImpl(this as GameSpinState, _$identity); - - - -@override -bool operator ==(Object other) { - final _this = this as GameSpinState; - return identical(this, other) || (other.runtimeType == runtimeType&&other is GameSpinState&&(identical(other.isSpinning, _this.isSpinning) || other.isSpinning == _this.isSpinning)&&(identical(other.failureOrResultOption, _this.failureOrResultOption) || other.failureOrResultOption == _this.failureOrResultOption)); -} - - -@override -int get hashCode { - final _this = this as GameSpinState; - return Object.hash(runtimeType,_this.isSpinning,_this.failureOrResultOption); -} - -@override -String toString() { - final _this = this as GameSpinState; - return 'GameSpinState(isSpinning: ${_this.isSpinning}, failureOrResultOption: ${_this.failureOrResultOption})'; -} - - -} - -/// @nodoc -abstract mixin class $GameSpinStateCopyWith<$Res> { - factory $GameSpinStateCopyWith(GameSpinState value, $Res Function(GameSpinState) _then) = _$GameSpinStateCopyWithImpl; -@useResult -$Res call({ - bool isSpinning, Option> failureOrResultOption -}); - - - - -} -/// @nodoc -class _$GameSpinStateCopyWithImpl<$Res> - implements $GameSpinStateCopyWith<$Res> { - _$GameSpinStateCopyWithImpl(this._self, this._then); - - final GameSpinState _self; - final $Res Function(GameSpinState) _then; - -/// Create a copy of GameSpinState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? isSpinning = null,Object? failureOrResultOption = null,}) { - return _then(GameSpinState( -isSpinning: null == isSpinning ? _self.isSpinning : isSpinning // ignore: cast_nullable_to_non_nullable -as bool,failureOrResultOption: null == failureOrResultOption ? _self.failureOrResultOption : failureOrResultOption // ignore: cast_nullable_to_non_nullable -as Option>, - )); -} - -} - - -/// Adds pattern-matching-related methods to [GameSpinState]. -extension GameSpinStatePatterns on GameSpinState { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _GameSpinState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _GameSpinState() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _GameSpinState value) $default,){ -final _that = this; -switch (_that) { -case _GameSpinState(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GameSpinState value)? $default,){ -final _that = this; -switch (_that) { -case _GameSpinState() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( bool isSpinning, Option> failureOrResultOption)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _GameSpinState() when $default != null: -return $default(_that.isSpinning,_that.failureOrResultOption);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( bool isSpinning, Option> failureOrResultOption) $default,) {final _that = this; -switch (_that) { -case _GameSpinState(): -return $default(_that.isSpinning,_that.failureOrResultOption);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isSpinning, Option> failureOrResultOption)? $default,) {final _that = this; -switch (_that) { -case _GameSpinState() when $default != null: -return $default(_that.isSpinning,_that.failureOrResultOption);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _GameSpinState implements GameSpinState { - const _GameSpinState({this.isSpinning = false, required this.failureOrResultOption}); - - -@override@JsonKey() final bool isSpinning; -@override final Option> failureOrResultOption; - -/// Create a copy of GameSpinState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$GameSpinStateCopyWith<_GameSpinState> get copyWith => __$GameSpinStateCopyWithImpl<_GameSpinState>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _GameSpinState&&(identical(other.isSpinning, isSpinning) || other.isSpinning == isSpinning)&&(identical(other.failureOrResultOption, failureOrResultOption) || other.failureOrResultOption == failureOrResultOption)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,isSpinning,failureOrResultOption); -} - -@override -String toString() { - return 'GameSpinState(isSpinning: $isSpinning, failureOrResultOption: $failureOrResultOption)'; -} - - -} - -/// @nodoc -abstract mixin class _$GameSpinStateCopyWith<$Res> implements $GameSpinStateCopyWith<$Res> { - factory _$GameSpinStateCopyWith(_GameSpinState value, $Res Function(_GameSpinState) _then) = __$GameSpinStateCopyWithImpl; -@override @useResult -$Res call({ - bool isSpinning, Option> failureOrResultOption -}); - - - - -} -/// @nodoc -class __$GameSpinStateCopyWithImpl<$Res> - implements _$GameSpinStateCopyWith<$Res> { - __$GameSpinStateCopyWithImpl(this._self, this._then); - - final _GameSpinState _self; - final $Res Function(_GameSpinState) _then; - -/// Create a copy of GameSpinState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? isSpinning = null,Object? failureOrResultOption = null,}) { - return _then(_GameSpinState( -isSpinning: null == isSpinning ? _self.isSpinning : isSpinning // ignore: cast_nullable_to_non_nullable -as bool,failureOrResultOption: null == failureOrResultOption ? _self.failureOrResultOption : failureOrResultOption // ignore: cast_nullable_to_non_nullable -as Option>, - )); -} - - -} - -// dart format on diff --git a/lib/application/game/game_spin/game_spin_event.dart b/lib/application/game/game_spin/game_spin_event.dart deleted file mode 100644 index 3500bbb..0000000 --- a/lib/application/game/game_spin/game_spin_event.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of 'game_spin_bloc.dart'; - -@freezed -abstract class GameSpinEvent with _$GameSpinEvent { - const factory GameSpinEvent.spun(String gameId) = _Spun; -} diff --git a/lib/application/game/game_spin/game_spin_state.dart b/lib/application/game/game_spin/game_spin_state.dart deleted file mode 100644 index 52c97b6..0000000 --- a/lib/application/game/game_spin/game_spin_state.dart +++ /dev/null @@ -1,12 +0,0 @@ -part of 'game_spin_bloc.dart'; - -@freezed -abstract class GameSpinState with _$GameSpinState { - const factory GameSpinState({ - @Default(false) bool isSpinning, - required Option> failureOrResultOption, - }) = _GameSpinState; - - factory GameSpinState.initial() => - GameSpinState(failureOrResultOption: none()); -} diff --git a/lib/application/voucher/voucher_loader/voucher_loader_bloc.dart b/lib/application/voucher/voucher_loader/voucher_loader_bloc.dart new file mode 100644 index 0000000..e1872cd --- /dev/null +++ b/lib/application/voucher/voucher_loader/voucher_loader_bloc.dart @@ -0,0 +1,40 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/wallet/wallet.dart'; + +part 'voucher_loader_event.dart'; +part 'voucher_loader_state.dart'; +part 'voucher_loader_bloc.freezed.dart'; + +/// Katalog voucher yang bisa ditukar (§9.1). +@injectable +class VoucherLoaderBloc extends Bloc { + final IVoucherRepository _repository; + + VoucherLoaderBloc(this._repository) : super(VoucherLoaderState.initial()) { + on(_onVoucherLoaderEvent); + } + + Future _onVoucherLoaderEvent( + VoucherLoaderEvent event, + Emitter emit, + ) { + return event.map( + fetched: (e) async { + emit(state.copyWith(isFetching: true, failureOption: none())); + + final result = await _repository.getVouchers(); + + final data = result.fold( + (f) => state.copyWith(failureOption: optionOf(f)), + (vouchers) => state.copyWith(vouchers: vouchers, hasLoaded: true), + ); + + emit(data.copyWith(isFetching: false)); + }, + ); + } +} diff --git a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart b/lib/application/voucher/voucher_loader/voucher_loader_bloc.freezed.dart similarity index 51% rename from lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart rename to lib/application/voucher/voucher_loader/voucher_loader_bloc.freezed.dart index 469a6e2..05e9989 100644 --- a/lib/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.freezed.dart +++ b/lib/application/voucher/voucher_loader/voucher_loader_bloc.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'ferris_wheel_loader_bloc.dart'; +part of 'voucher_loader_bloc.dart'; // ************************************************************************** // FreezedGenerator @@ -13,7 +13,7 @@ part of 'ferris_wheel_loader_bloc.dart'; // dart format off T _$identity(T value) => value; /// @nodoc -mixin _$FerrisWheelLoaderEvent { +mixin _$VoucherLoaderEvent { @@ -21,7 +21,7 @@ mixin _$FerrisWheelLoaderEvent { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is FerrisWheelLoaderEvent); + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherLoaderEvent); } @@ -30,20 +30,20 @@ int get hashCode => runtimeType.hashCode; @override String toString() { - return 'FerrisWheelLoaderEvent()'; + return 'VoucherLoaderEvent()'; } } /// @nodoc -class $FerrisWheelLoaderEventCopyWith<$Res> { -$FerrisWheelLoaderEventCopyWith(FerrisWheelLoaderEvent _, $Res Function(FerrisWheelLoaderEvent) __); +class $VoucherLoaderEventCopyWith<$Res> { +$VoucherLoaderEventCopyWith(VoucherLoaderEvent _, $Res Function(VoucherLoaderEvent) __); } -/// Adds pattern-matching-related methods to [FerrisWheelLoaderEvent]. -extension FerrisWheelLoaderEventPatterns on FerrisWheelLoaderEvent { +/// Adds pattern-matching-related methods to [VoucherLoaderEvent]. +extension VoucherLoaderEventPatterns on VoucherLoaderEvent { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -175,7 +175,7 @@ return fetched();case _: /// @nodoc -class _Fetched implements FerrisWheelLoaderEvent { +class _Fetched implements VoucherLoaderEvent { const _Fetched(); @@ -195,7 +195,7 @@ int get hashCode => runtimeType.hashCode; @override String toString() { - return 'FerrisWheelLoaderEvent.fetched()'; + return 'VoucherLoaderEvent.fetched()'; } @@ -205,84 +205,76 @@ String toString() { /// @nodoc -mixin _$FerrisWheelLoaderState { +mixin _$VoucherLoaderState { - Game get ferrisWheel; Option get failureOptionFerrisWheel; bool get isFetching; -/// Create a copy of FerrisWheelLoaderState + List get vouchers; Option get failureOption; bool get isFetching; bool get hasLoaded; +/// Create a copy of VoucherLoaderState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$FerrisWheelLoaderStateCopyWith get copyWith => _$FerrisWheelLoaderStateCopyWithImpl(this as FerrisWheelLoaderState, _$identity); +$VoucherLoaderStateCopyWith get copyWith => _$VoucherLoaderStateCopyWithImpl(this as VoucherLoaderState, _$identity); @override bool operator ==(Object other) { - final _this = this as FerrisWheelLoaderState; - return identical(this, other) || (other.runtimeType == runtimeType&&other is FerrisWheelLoaderState&&(identical(other.ferrisWheel, _this.ferrisWheel) || other.ferrisWheel == _this.ferrisWheel)&&(identical(other.failureOptionFerrisWheel, _this.failureOptionFerrisWheel) || other.failureOptionFerrisWheel == _this.failureOptionFerrisWheel)&&(identical(other.isFetching, _this.isFetching) || other.isFetching == _this.isFetching)); + final _this = this as VoucherLoaderState; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherLoaderState&&const DeepCollectionEquality().equals(other.vouchers, _this.vouchers)&&(identical(other.failureOption, _this.failureOption) || other.failureOption == _this.failureOption)&&(identical(other.isFetching, _this.isFetching) || other.isFetching == _this.isFetching)&&(identical(other.hasLoaded, _this.hasLoaded) || other.hasLoaded == _this.hasLoaded)); } @override int get hashCode { - final _this = this as FerrisWheelLoaderState; - return Object.hash(runtimeType,_this.ferrisWheel,_this.failureOptionFerrisWheel,_this.isFetching); + final _this = this as VoucherLoaderState; + return Object.hash(runtimeType,const DeepCollectionEquality().hash(_this.vouchers),_this.failureOption,_this.isFetching,_this.hasLoaded); } @override String toString() { - final _this = this as FerrisWheelLoaderState; - return 'FerrisWheelLoaderState(ferrisWheel: ${_this.ferrisWheel}, failureOptionFerrisWheel: ${_this.failureOptionFerrisWheel}, isFetching: ${_this.isFetching})'; + final _this = this as VoucherLoaderState; + return 'VoucherLoaderState(vouchers: ${_this.vouchers}, failureOption: ${_this.failureOption}, isFetching: ${_this.isFetching}, hasLoaded: ${_this.hasLoaded})'; } } /// @nodoc -abstract mixin class $FerrisWheelLoaderStateCopyWith<$Res> { - factory $FerrisWheelLoaderStateCopyWith(FerrisWheelLoaderState value, $Res Function(FerrisWheelLoaderState) _then) = _$FerrisWheelLoaderStateCopyWithImpl; +abstract mixin class $VoucherLoaderStateCopyWith<$Res> { + factory $VoucherLoaderStateCopyWith(VoucherLoaderState value, $Res Function(VoucherLoaderState) _then) = _$VoucherLoaderStateCopyWithImpl; @useResult $Res call({ - Game ferrisWheel, Option failureOptionFerrisWheel, bool isFetching + List vouchers, Option failureOption, bool isFetching, bool hasLoaded }); -$GameCopyWith<$Res> get ferrisWheel; + } /// @nodoc -class _$FerrisWheelLoaderStateCopyWithImpl<$Res> - implements $FerrisWheelLoaderStateCopyWith<$Res> { - _$FerrisWheelLoaderStateCopyWithImpl(this._self, this._then); +class _$VoucherLoaderStateCopyWithImpl<$Res> + implements $VoucherLoaderStateCopyWith<$Res> { + _$VoucherLoaderStateCopyWithImpl(this._self, this._then); - final FerrisWheelLoaderState _self; - final $Res Function(FerrisWheelLoaderState) _then; + final VoucherLoaderState _self; + final $Res Function(VoucherLoaderState) _then; -/// Create a copy of FerrisWheelLoaderState +/// Create a copy of VoucherLoaderState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? ferrisWheel = null,Object? failureOptionFerrisWheel = null,Object? isFetching = null,}) { - return _then(FerrisWheelLoaderState( -ferrisWheel: null == ferrisWheel ? _self.ferrisWheel : ferrisWheel // ignore: cast_nullable_to_non_nullable -as Game,failureOptionFerrisWheel: null == failureOptionFerrisWheel ? _self.failureOptionFerrisWheel : failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable -as Option,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable +@pragma('vm:prefer-inline') @override $Res call({Object? vouchers = null,Object? failureOption = null,Object? isFetching = null,Object? hasLoaded = null,}) { + return _then(VoucherLoaderState( +vouchers: null == vouchers ? _self.vouchers : vouchers // ignore: cast_nullable_to_non_nullable +as List,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable +as Option,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable +as bool,hasLoaded: null == hasLoaded ? _self.hasLoaded : hasLoaded // ignore: cast_nullable_to_non_nullable as bool, )); } -/// Create a copy of FerrisWheelLoaderState -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$GameCopyWith<$Res> get ferrisWheel { - - return $GameCopyWith<$Res>(_self.ferrisWheel, (value) { - return _then(_self.copyWith(ferrisWheel: value)); - }); -} + } -/// Adds pattern-matching-related methods to [FerrisWheelLoaderState]. -extension FerrisWheelLoaderStatePatterns on FerrisWheelLoaderState { +/// Adds pattern-matching-related methods to [VoucherLoaderState]. +extension VoucherLoaderStatePatterns on VoucherLoaderState { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -295,10 +287,10 @@ extension FerrisWheelLoaderStatePatterns on FerrisWheelLoaderState { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _FerrisWheelLoaderState value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherLoaderState value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _FerrisWheelLoaderState() when $default != null: +case _VoucherLoaderState() when $default != null: return $default(_that);case _: return orElse(); @@ -317,10 +309,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _FerrisWheelLoaderState value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _VoucherLoaderState value) $default,){ final _that = this; switch (_that) { -case _FerrisWheelLoaderState(): +case _VoucherLoaderState(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -338,10 +330,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _FerrisWheelLoaderState value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherLoaderState value)? $default,){ final _that = this; switch (_that) { -case _FerrisWheelLoaderState() when $default != null: +case _VoucherLoaderState() when $default != null: return $default(_that);case _: return null; @@ -359,10 +351,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( Game ferrisWheel, Option failureOptionFerrisWheel, bool isFetching)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( List vouchers, Option failureOption, bool isFetching, bool hasLoaded)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _FerrisWheelLoaderState() when $default != null: -return $default(_that.ferrisWheel,_that.failureOptionFerrisWheel,_that.isFetching);case _: +case _VoucherLoaderState() when $default != null: +return $default(_that.vouchers,_that.failureOption,_that.isFetching,_that.hasLoaded);case _: return orElse(); } @@ -380,10 +372,10 @@ return $default(_that.ferrisWheel,_that.failureOptionFerrisWheel,_that.isFetchin /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( Game ferrisWheel, Option failureOptionFerrisWheel, bool isFetching) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( List vouchers, Option failureOption, bool isFetching, bool hasLoaded) $default,) {final _that = this; switch (_that) { -case _FerrisWheelLoaderState(): -return $default(_that.ferrisWheel,_that.failureOptionFerrisWheel,_that.isFetching);case _: +case _VoucherLoaderState(): +return $default(_that.vouchers,_that.failureOption,_that.isFetching,_that.hasLoaded);case _: throw StateError('Unexpected subclass'); } @@ -400,10 +392,10 @@ return $default(_that.ferrisWheel,_that.failureOptionFerrisWheel,_that.isFetchin /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( Game ferrisWheel, Option failureOptionFerrisWheel, bool isFetching)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List vouchers, Option failureOption, bool isFetching, bool hasLoaded)? $default,) {final _that = this; switch (_that) { -case _FerrisWheelLoaderState() when $default != null: -return $default(_that.ferrisWheel,_that.failureOptionFerrisWheel,_that.isFetching);case _: +case _VoucherLoaderState() when $default != null: +return $default(_that.vouchers,_that.failureOption,_that.isFetching,_that.hasLoaded);case _: return null; } @@ -414,82 +406,81 @@ return $default(_that.ferrisWheel,_that.failureOptionFerrisWheel,_that.isFetchin /// @nodoc -class _FerrisWheelLoaderState implements FerrisWheelLoaderState { - const _FerrisWheelLoaderState({required this.ferrisWheel, required this.failureOptionFerrisWheel, this.isFetching = false}); +class _VoucherLoaderState implements VoucherLoaderState { + const _VoucherLoaderState({required List vouchers, required this.failureOption, this.isFetching = false, this.hasLoaded = false}): _vouchers = vouchers; -@override final Game ferrisWheel; -@override final Option failureOptionFerrisWheel; -@override@JsonKey() final bool isFetching; + final List _vouchers; +@override List get vouchers { + if (_vouchers is EqualUnmodifiableListView) return _vouchers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_vouchers); +} -/// Create a copy of FerrisWheelLoaderState +@override final Option failureOption; +@override@JsonKey() final bool isFetching; +@override@JsonKey() final bool hasLoaded; + +/// Create a copy of VoucherLoaderState /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$FerrisWheelLoaderStateCopyWith<_FerrisWheelLoaderState> get copyWith => __$FerrisWheelLoaderStateCopyWithImpl<_FerrisWheelLoaderState>(this, _$identity); +_$VoucherLoaderStateCopyWith<_VoucherLoaderState> get copyWith => __$VoucherLoaderStateCopyWithImpl<_VoucherLoaderState>(this, _$identity); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _FerrisWheelLoaderState&&(identical(other.ferrisWheel, ferrisWheel) || other.ferrisWheel == ferrisWheel)&&(identical(other.failureOptionFerrisWheel, failureOptionFerrisWheel) || other.failureOptionFerrisWheel == failureOptionFerrisWheel)&&(identical(other.isFetching, isFetching) || other.isFetching == isFetching)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherLoaderState&&const DeepCollectionEquality().equals(other.vouchers, _vouchers)&&(identical(other.failureOption, failureOption) || other.failureOption == failureOption)&&(identical(other.isFetching, isFetching) || other.isFetching == isFetching)&&(identical(other.hasLoaded, hasLoaded) || other.hasLoaded == hasLoaded)); } @override int get hashCode { - return Object.hash(runtimeType,ferrisWheel,failureOptionFerrisWheel,isFetching); + return Object.hash(runtimeType,const DeepCollectionEquality().hash(_vouchers),failureOption,isFetching,hasLoaded); } @override String toString() { - return 'FerrisWheelLoaderState(ferrisWheel: $ferrisWheel, failureOptionFerrisWheel: $failureOptionFerrisWheel, isFetching: $isFetching)'; + return 'VoucherLoaderState(vouchers: $vouchers, failureOption: $failureOption, isFetching: $isFetching, hasLoaded: $hasLoaded)'; } } /// @nodoc -abstract mixin class _$FerrisWheelLoaderStateCopyWith<$Res> implements $FerrisWheelLoaderStateCopyWith<$Res> { - factory _$FerrisWheelLoaderStateCopyWith(_FerrisWheelLoaderState value, $Res Function(_FerrisWheelLoaderState) _then) = __$FerrisWheelLoaderStateCopyWithImpl; +abstract mixin class _$VoucherLoaderStateCopyWith<$Res> implements $VoucherLoaderStateCopyWith<$Res> { + factory _$VoucherLoaderStateCopyWith(_VoucherLoaderState value, $Res Function(_VoucherLoaderState) _then) = __$VoucherLoaderStateCopyWithImpl; @override @useResult $Res call({ - Game ferrisWheel, Option failureOptionFerrisWheel, bool isFetching + List vouchers, Option failureOption, bool isFetching, bool hasLoaded }); -@override $GameCopyWith<$Res> get ferrisWheel; + } /// @nodoc -class __$FerrisWheelLoaderStateCopyWithImpl<$Res> - implements _$FerrisWheelLoaderStateCopyWith<$Res> { - __$FerrisWheelLoaderStateCopyWithImpl(this._self, this._then); +class __$VoucherLoaderStateCopyWithImpl<$Res> + implements _$VoucherLoaderStateCopyWith<$Res> { + __$VoucherLoaderStateCopyWithImpl(this._self, this._then); - final _FerrisWheelLoaderState _self; - final $Res Function(_FerrisWheelLoaderState) _then; + final _VoucherLoaderState _self; + final $Res Function(_VoucherLoaderState) _then; -/// Create a copy of FerrisWheelLoaderState +/// Create a copy of VoucherLoaderState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? ferrisWheel = null,Object? failureOptionFerrisWheel = null,Object? isFetching = null,}) { - return _then(_FerrisWheelLoaderState( -ferrisWheel: null == ferrisWheel ? _self.ferrisWheel : ferrisWheel // ignore: cast_nullable_to_non_nullable -as Game,failureOptionFerrisWheel: null == failureOptionFerrisWheel ? _self.failureOptionFerrisWheel : failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable -as Option,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable +@override @pragma('vm:prefer-inline') $Res call({Object? vouchers = null,Object? failureOption = null,Object? isFetching = null,Object? hasLoaded = null,}) { + return _then(_VoucherLoaderState( +vouchers: null == vouchers ? _self._vouchers : vouchers // ignore: cast_nullable_to_non_nullable +as List,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable +as Option,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable +as bool,hasLoaded: null == hasLoaded ? _self.hasLoaded : hasLoaded // ignore: cast_nullable_to_non_nullable as bool, )); } -/// Create a copy of FerrisWheelLoaderState -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$GameCopyWith<$Res> get ferrisWheel { - - return $GameCopyWith<$Res>(_self.ferrisWheel, (value) { - return _then(_self.copyWith(ferrisWheel: value)); - }); -} + } // dart format on diff --git a/lib/application/voucher/voucher_loader/voucher_loader_event.dart b/lib/application/voucher/voucher_loader/voucher_loader_event.dart new file mode 100644 index 0000000..5fd54f6 --- /dev/null +++ b/lib/application/voucher/voucher_loader/voucher_loader_event.dart @@ -0,0 +1,6 @@ +part of 'voucher_loader_bloc.dart'; + +@freezed +abstract class VoucherLoaderEvent with _$VoucherLoaderEvent { + const factory VoucherLoaderEvent.fetched() = _Fetched; +} diff --git a/lib/application/voucher/voucher_loader/voucher_loader_state.dart b/lib/application/voucher/voucher_loader/voucher_loader_state.dart new file mode 100644 index 0000000..a40f80f --- /dev/null +++ b/lib/application/voucher/voucher_loader/voucher_loader_state.dart @@ -0,0 +1,14 @@ +part of 'voucher_loader_bloc.dart'; + +@freezed +abstract class VoucherLoaderState with _$VoucherLoaderState { + const factory VoucherLoaderState({ + required List vouchers, + required Option failureOption, + @Default(false) bool isFetching, + @Default(false) bool hasLoaded, + }) = _VoucherLoaderState; + + factory VoucherLoaderState.initial() => + VoucherLoaderState(vouchers: const [], failureOption: none()); +} diff --git a/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.dart b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.dart new file mode 100644 index 0000000..42e4fb5 --- /dev/null +++ b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.dart @@ -0,0 +1,63 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../common/function/app_function.dart'; +import '../../../domain/wallet/wallet.dart'; + +part 'voucher_redeem_form_event.dart'; +part 'voucher_redeem_form_state.dart'; +part 'voucher_redeem_form_bloc.freezed.dart'; + +/// Tukar EnakPoint ke satu voucher (§9.2). Satu bloc per voucher, supaya +/// `Idempotency-Key` tidak terbawa ke voucher lain. +@injectable +class VoucherRedeemFormBloc + extends Bloc { + final IVoucherRepository _repository; + + VoucherRedeemFormBloc(this._repository) + : super(VoucherRedeemFormState.initial()) { + on(_onVoucherRedeemFormEvent); + } + + Future _onVoucherRedeemFormEvent( + VoucherRedeemFormEvent event, + Emitter emit, + ) { + return event.map( + submitted: (e) async { + // Key baru untuk setiap konfirmasi, kecuali mengulang request yang + // sebelumnya gagal karena jaringan. + final key = state.idempotencyKey ?? generateUuidV4(); + emit( + state.copyWith( + isSubmitting: true, + idempotencyKey: key, + failureOption: none(), + ), + ); + + final result = await _repository.redeem( + voucherId: e.voucherId, + pin: e.pin, + idempotencyKey: key, + ); + + emit( + result + .fold( + (f) => state.copyWith( + failureOption: optionOf(f), + idempotencyKey: f.isNetworkError ? key : null, + ), + (redeemResult) => + state.copyWith(result: redeemResult, idempotencyKey: null), + ) + .copyWith(isSubmitting: false), + ); + }, + ); + } +} diff --git a/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.freezed.dart b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.freezed.dart new file mode 100644 index 0000000..ac66221 --- /dev/null +++ b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.freezed.dart @@ -0,0 +1,579 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'voucher_redeem_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$VoucherRedeemFormEvent { + + String get voucherId; String get pin; +/// Create a copy of VoucherRedeemFormEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherRedeemFormEventCopyWith get copyWith => _$VoucherRedeemFormEventCopyWithImpl(this as VoucherRedeemFormEvent, _$identity); + + + +@override +bool operator ==(Object other) { + final _this = this as VoucherRedeemFormEvent; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedeemFormEvent&&(identical(other.voucherId, _this.voucherId) || other.voucherId == _this.voucherId)&&(identical(other.pin, _this.pin) || other.pin == _this.pin)); +} + + +@override +int get hashCode { + final _this = this as VoucherRedeemFormEvent; + return Object.hash(runtimeType,_this.voucherId,_this.pin); +} + +@override +String toString() { + final _this = this as VoucherRedeemFormEvent; + return 'VoucherRedeemFormEvent(voucherId: ${_this.voucherId}, pin: ${_this.pin})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherRedeemFormEventCopyWith<$Res> { + factory $VoucherRedeemFormEventCopyWith(VoucherRedeemFormEvent value, $Res Function(VoucherRedeemFormEvent) _then) = _$VoucherRedeemFormEventCopyWithImpl; +@useResult +$Res call({ + String voucherId, String pin +}); + + + + +} +/// @nodoc +class _$VoucherRedeemFormEventCopyWithImpl<$Res> + implements $VoucherRedeemFormEventCopyWith<$Res> { + _$VoucherRedeemFormEventCopyWithImpl(this._self, this._then); + + final VoucherRedeemFormEvent _self; + final $Res Function(VoucherRedeemFormEvent) _then; + +/// Create a copy of VoucherRedeemFormEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? voucherId = null,Object? pin = null,}) { + return _then(VoucherRedeemFormEvent.submitted( +voucherId: null == voucherId ? _self.voucherId : voucherId // ignore: cast_nullable_to_non_nullable +as String,pin: null == pin ? _self.pin : pin // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [VoucherRedeemFormEvent]. +extension VoucherRedeemFormEventPatterns on VoucherRedeemFormEvent { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( _Submitted value)? submitted,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Submitted() when submitted != null: +return submitted(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( _Submitted value) submitted,}){ +final _that = this; +switch (_that) { +case _Submitted(): +return submitted(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( _Submitted value)? submitted,}){ +final _that = this; +switch (_that) { +case _Submitted() when submitted != null: +return submitted(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( String voucherId, String pin)? submitted,required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Submitted() when submitted != null: +return submitted(_that.voucherId,_that.pin);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( String voucherId, String pin) submitted,}) {final _that = this; +switch (_that) { +case _Submitted(): +return submitted(_that.voucherId,_that.pin);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( String voucherId, String pin)? submitted,}) {final _that = this; +switch (_that) { +case _Submitted() when submitted != null: +return submitted(_that.voucherId,_that.pin);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _Submitted implements VoucherRedeemFormEvent { + const _Submitted({required this.voucherId, required this.pin}); + + +@override final String voucherId; +@override final String pin; + +/// Create a copy of VoucherRedeemFormEvent +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SubmittedCopyWith<_Submitted> get copyWith => __$SubmittedCopyWithImpl<_Submitted>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Submitted&&(identical(other.voucherId, voucherId) || other.voucherId == voucherId)&&(identical(other.pin, pin) || other.pin == pin)); +} + + +@override +int get hashCode { + return Object.hash(runtimeType,voucherId,pin); +} + +@override +String toString() { + return 'VoucherRedeemFormEvent.submitted(voucherId: $voucherId, pin: $pin)'; +} + + +} + +/// @nodoc +abstract mixin class _$SubmittedCopyWith<$Res> implements $VoucherRedeemFormEventCopyWith<$Res> { + factory _$SubmittedCopyWith(_Submitted value, $Res Function(_Submitted) _then) = __$SubmittedCopyWithImpl; +@override @useResult +$Res call({ + String voucherId, String pin +}); + + + + +} +/// @nodoc +class __$SubmittedCopyWithImpl<$Res> + implements _$SubmittedCopyWith<$Res> { + __$SubmittedCopyWithImpl(this._self, this._then); + + final _Submitted _self; + final $Res Function(_Submitted) _then; + +/// Create a copy of VoucherRedeemFormEvent +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? voucherId = null,Object? pin = null,}) { + return _then(_Submitted( +voucherId: null == voucherId ? _self.voucherId : voucherId // ignore: cast_nullable_to_non_nullable +as String,pin: null == pin ? _self.pin : pin // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc +mixin _$VoucherRedeemFormState { + + VoucherRedeemResult? get result; Option get failureOption; bool get isSubmitting; String? get idempotencyKey; +/// Create a copy of VoucherRedeemFormState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherRedeemFormStateCopyWith get copyWith => _$VoucherRedeemFormStateCopyWithImpl(this as VoucherRedeemFormState, _$identity); + + + +@override +bool operator ==(Object other) { + final _this = this as VoucherRedeemFormState; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedeemFormState&&(identical(other.result, _this.result) || other.result == _this.result)&&(identical(other.failureOption, _this.failureOption) || other.failureOption == _this.failureOption)&&(identical(other.isSubmitting, _this.isSubmitting) || other.isSubmitting == _this.isSubmitting)&&(identical(other.idempotencyKey, _this.idempotencyKey) || other.idempotencyKey == _this.idempotencyKey)); +} + + +@override +int get hashCode { + final _this = this as VoucherRedeemFormState; + return Object.hash(runtimeType,_this.result,_this.failureOption,_this.isSubmitting,_this.idempotencyKey); +} + +@override +String toString() { + final _this = this as VoucherRedeemFormState; + return 'VoucherRedeemFormState(result: ${_this.result}, failureOption: ${_this.failureOption}, isSubmitting: ${_this.isSubmitting}, idempotencyKey: ${_this.idempotencyKey})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherRedeemFormStateCopyWith<$Res> { + factory $VoucherRedeemFormStateCopyWith(VoucherRedeemFormState value, $Res Function(VoucherRedeemFormState) _then) = _$VoucherRedeemFormStateCopyWithImpl; +@useResult +$Res call({ + VoucherRedeemResult? result, Option failureOption, bool isSubmitting, String? idempotencyKey +}); + + +$VoucherRedeemResultCopyWith<$Res>? get result; + +} +/// @nodoc +class _$VoucherRedeemFormStateCopyWithImpl<$Res> + implements $VoucherRedeemFormStateCopyWith<$Res> { + _$VoucherRedeemFormStateCopyWithImpl(this._self, this._then); + + final VoucherRedeemFormState _self; + final $Res Function(VoucherRedeemFormState) _then; + +/// Create a copy of VoucherRedeemFormState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? result = freezed,Object? failureOption = null,Object? isSubmitting = null,Object? idempotencyKey = freezed,}) { + return _then(VoucherRedeemFormState( +result: freezed == result ? _self.result : result // ignore: cast_nullable_to_non_nullable +as VoucherRedeemResult?,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable +as Option,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable +as bool,idempotencyKey: freezed == idempotencyKey ? _self.idempotencyKey : idempotencyKey // ignore: cast_nullable_to_non_nullable +as String?, + )); +} +/// Create a copy of VoucherRedeemFormState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$VoucherRedeemResultCopyWith<$Res>? get result { + if (_self.result == null) { + return null; + } + + return $VoucherRedeemResultCopyWith<$Res>(_self.result!, (value) { + return _then(_self.copyWith(result: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [VoucherRedeemFormState]. +extension VoucherRedeemFormStatePatterns on VoucherRedeemFormState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherRedeemFormState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _VoucherRedeemFormState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _VoucherRedeemFormState value) $default,){ +final _that = this; +switch (_that) { +case _VoucherRedeemFormState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherRedeemFormState value)? $default,){ +final _that = this; +switch (_that) { +case _VoucherRedeemFormState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( VoucherRedeemResult? result, Option failureOption, bool isSubmitting, String? idempotencyKey)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _VoucherRedeemFormState() when $default != null: +return $default(_that.result,_that.failureOption,_that.isSubmitting,_that.idempotencyKey);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( VoucherRedeemResult? result, Option failureOption, bool isSubmitting, String? idempotencyKey) $default,) {final _that = this; +switch (_that) { +case _VoucherRedeemFormState(): +return $default(_that.result,_that.failureOption,_that.isSubmitting,_that.idempotencyKey);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( VoucherRedeemResult? result, Option failureOption, bool isSubmitting, String? idempotencyKey)? $default,) {final _that = this; +switch (_that) { +case _VoucherRedeemFormState() when $default != null: +return $default(_that.result,_that.failureOption,_that.isSubmitting,_that.idempotencyKey);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _VoucherRedeemFormState implements VoucherRedeemFormState { + const _VoucherRedeemFormState({this.result, required this.failureOption, this.isSubmitting = false, this.idempotencyKey}); + + +@override final VoucherRedeemResult? result; +@override final Option failureOption; +@override@JsonKey() final bool isSubmitting; +@override final String? idempotencyKey; + +/// Create a copy of VoucherRedeemFormState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherRedeemFormStateCopyWith<_VoucherRedeemFormState> get copyWith => __$VoucherRedeemFormStateCopyWithImpl<_VoucherRedeemFormState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherRedeemFormState&&(identical(other.result, result) || other.result == result)&&(identical(other.failureOption, failureOption) || other.failureOption == failureOption)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)&&(identical(other.idempotencyKey, idempotencyKey) || other.idempotencyKey == idempotencyKey)); +} + + +@override +int get hashCode { + return Object.hash(runtimeType,result,failureOption,isSubmitting,idempotencyKey); +} + +@override +String toString() { + return 'VoucherRedeemFormState(result: $result, failureOption: $failureOption, isSubmitting: $isSubmitting, idempotencyKey: $idempotencyKey)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherRedeemFormStateCopyWith<$Res> implements $VoucherRedeemFormStateCopyWith<$Res> { + factory _$VoucherRedeemFormStateCopyWith(_VoucherRedeemFormState value, $Res Function(_VoucherRedeemFormState) _then) = __$VoucherRedeemFormStateCopyWithImpl; +@override @useResult +$Res call({ + VoucherRedeemResult? result, Option failureOption, bool isSubmitting, String? idempotencyKey +}); + + +@override $VoucherRedeemResultCopyWith<$Res>? get result; + +} +/// @nodoc +class __$VoucherRedeemFormStateCopyWithImpl<$Res> + implements _$VoucherRedeemFormStateCopyWith<$Res> { + __$VoucherRedeemFormStateCopyWithImpl(this._self, this._then); + + final _VoucherRedeemFormState _self; + final $Res Function(_VoucherRedeemFormState) _then; + +/// Create a copy of VoucherRedeemFormState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? result = freezed,Object? failureOption = null,Object? isSubmitting = null,Object? idempotencyKey = freezed,}) { + return _then(_VoucherRedeemFormState( +result: freezed == result ? _self.result : result // ignore: cast_nullable_to_non_nullable +as VoucherRedeemResult?,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable +as Option,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable +as bool,idempotencyKey: freezed == idempotencyKey ? _self.idempotencyKey : idempotencyKey // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +/// Create a copy of VoucherRedeemFormState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$VoucherRedeemResultCopyWith<$Res>? get result { + if (_self.result == null) { + return null; + } + + return $VoucherRedeemResultCopyWith<$Res>(_self.result!, (value) { + return _then(_self.copyWith(result: value)); + }); +} +} + +// dart format on diff --git a/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_event.dart b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_event.dart new file mode 100644 index 0000000..302e8f5 --- /dev/null +++ b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_event.dart @@ -0,0 +1,10 @@ +part of 'voucher_redeem_form_bloc.dart'; + +@freezed +abstract class VoucherRedeemFormEvent with _$VoucherRedeemFormEvent { + /// [pin] hanya diteruskan ke API, tidak disimpan di state. + const factory VoucherRedeemFormEvent.submitted({ + required String voucherId, + required String pin, + }) = _Submitted; +} diff --git a/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_state.dart b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_state.dart new file mode 100644 index 0000000..e11f7c6 --- /dev/null +++ b/lib/application/voucher/voucher_redeem_form/voucher_redeem_form_state.dart @@ -0,0 +1,14 @@ +part of 'voucher_redeem_form_bloc.dart'; + +@freezed +abstract class VoucherRedeemFormState with _$VoucherRedeemFormState { + const factory VoucherRedeemFormState({ + VoucherRedeemResult? result, + required Option failureOption, + @Default(false) bool isSubmitting, + String? idempotencyKey, + }) = _VoucherRedeemFormState; + + factory VoucherRedeemFormState.initial() => + VoucherRedeemFormState(failureOption: none()); +} diff --git a/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.dart b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.dart new file mode 100644 index 0000000..fb4a0d9 --- /dev/null +++ b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.dart @@ -0,0 +1,73 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/wallet/wallet.dart'; + +part 'voucher_redemption_loader_event.dart'; +part 'voucher_redemption_loader_state.dart'; +part 'voucher_redemption_loader_bloc.freezed.dart'; + +/// "Voucher saya" (§9.3). +@injectable +class VoucherRedemptionLoaderBloc + extends Bloc { + final IVoucherRepository _repository; + + static const int _limit = 20; + + VoucherRedemptionLoaderBloc(this._repository) + : super(VoucherRedemptionLoaderState.initial()) { + on(_onVoucherRedemptionLoaderEvent); + } + + Future _onVoucherRedemptionLoaderEvent( + VoucherRedemptionLoaderEvent event, + Emitter emit, + ) { + return event.map( + refreshed: (e) async { + emit(state.copyWith(isFetching: true, failureOption: none())); + + final result = await _repository.getRedemptions(page: 1, limit: _limit); + + emit( + result + .fold( + (f) => state.copyWith(failureOption: optionOf(f)), + (page) => state.copyWith( + items: page.items, + page: page.page, + totalPages: page.totalPages, + hasLoaded: true, + ), + ) + .copyWith(isFetching: false), + ); + }, + loadedMore: (e) async { + if (state.isFetching || state.isLoadingMore || !state.hasMore) return; + emit(state.copyWith(isLoadingMore: true, failureOption: none())); + + final result = await _repository.getRedemptions( + page: state.page + 1, + limit: _limit, + ); + + emit( + result + .fold( + (f) => state.copyWith(failureOption: optionOf(f)), + (page) => state.copyWith( + items: [...state.items, ...page.items], + page: page.page, + totalPages: page.totalPages, + ), + ) + .copyWith(isLoadingMore: false), + ); + }, + ); + } +} diff --git a/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.freezed.dart b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.freezed.dart new file mode 100644 index 0000000..1dc60bf --- /dev/null +++ b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.freezed.dart @@ -0,0 +1,533 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'voucher_redemption_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$VoucherRedemptionLoaderEvent { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedemptionLoaderEvent); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'VoucherRedemptionLoaderEvent()'; +} + + +} + +/// @nodoc +class $VoucherRedemptionLoaderEventCopyWith<$Res> { +$VoucherRedemptionLoaderEventCopyWith(VoucherRedemptionLoaderEvent _, $Res Function(VoucherRedemptionLoaderEvent) __); +} + + +/// Adds pattern-matching-related methods to [VoucherRedemptionLoaderEvent]. +extension VoucherRedemptionLoaderEventPatterns on VoucherRedemptionLoaderEvent { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( _Refreshed value)? refreshed,TResult Function( _LoadedMore value)? loadedMore,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Refreshed() when refreshed != null: +return refreshed(_that);case _LoadedMore() when loadedMore != null: +return loadedMore(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( _Refreshed value) refreshed,required TResult Function( _LoadedMore value) loadedMore,}){ +final _that = this; +switch (_that) { +case _Refreshed(): +return refreshed(_that);case _LoadedMore(): +return loadedMore(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( _Refreshed value)? refreshed,TResult? Function( _LoadedMore value)? loadedMore,}){ +final _that = this; +switch (_that) { +case _Refreshed() when refreshed != null: +return refreshed(_that);case _LoadedMore() when loadedMore != null: +return loadedMore(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function()? refreshed,TResult Function()? loadedMore,required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Refreshed() when refreshed != null: +return refreshed();case _LoadedMore() when loadedMore != null: +return loadedMore();case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function() refreshed,required TResult Function() loadedMore,}) {final _that = this; +switch (_that) { +case _Refreshed(): +return refreshed();case _LoadedMore(): +return loadedMore();case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function()? refreshed,TResult? Function()? loadedMore,}) {final _that = this; +switch (_that) { +case _Refreshed() when refreshed != null: +return refreshed();case _LoadedMore() when loadedMore != null: +return loadedMore();case _: + return null; + +} +} + +} + +/// @nodoc + + +class _Refreshed implements VoucherRedemptionLoaderEvent { + const _Refreshed(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Refreshed); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'VoucherRedemptionLoaderEvent.refreshed()'; +} + + +} + + + + +/// @nodoc + + +class _LoadedMore implements VoucherRedemptionLoaderEvent { + const _LoadedMore(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoadedMore); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'VoucherRedemptionLoaderEvent.loadedMore()'; +} + + +} + + + + +/// @nodoc +mixin _$VoucherRedemptionLoaderState { + + List get items; int get page; int get totalPages; Option get failureOption; bool get isFetching; bool get isLoadingMore; bool get hasLoaded; +/// Create a copy of VoucherRedemptionLoaderState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherRedemptionLoaderStateCopyWith get copyWith => _$VoucherRedemptionLoaderStateCopyWithImpl(this as VoucherRedemptionLoaderState, _$identity); + + + +@override +bool operator ==(Object other) { + final _this = this as VoucherRedemptionLoaderState; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedemptionLoaderState&&const DeepCollectionEquality().equals(other.items, _this.items)&&(identical(other.page, _this.page) || other.page == _this.page)&&(identical(other.totalPages, _this.totalPages) || other.totalPages == _this.totalPages)&&(identical(other.failureOption, _this.failureOption) || other.failureOption == _this.failureOption)&&(identical(other.isFetching, _this.isFetching) || other.isFetching == _this.isFetching)&&(identical(other.isLoadingMore, _this.isLoadingMore) || other.isLoadingMore == _this.isLoadingMore)&&(identical(other.hasLoaded, _this.hasLoaded) || other.hasLoaded == _this.hasLoaded)); +} + + +@override +int get hashCode { + final _this = this as VoucherRedemptionLoaderState; + return Object.hash(runtimeType,const DeepCollectionEquality().hash(_this.items),_this.page,_this.totalPages,_this.failureOption,_this.isFetching,_this.isLoadingMore,_this.hasLoaded); +} + +@override +String toString() { + final _this = this as VoucherRedemptionLoaderState; + return 'VoucherRedemptionLoaderState(items: ${_this.items}, page: ${_this.page}, totalPages: ${_this.totalPages}, failureOption: ${_this.failureOption}, isFetching: ${_this.isFetching}, isLoadingMore: ${_this.isLoadingMore}, hasLoaded: ${_this.hasLoaded})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherRedemptionLoaderStateCopyWith<$Res> { + factory $VoucherRedemptionLoaderStateCopyWith(VoucherRedemptionLoaderState value, $Res Function(VoucherRedemptionLoaderState) _then) = _$VoucherRedemptionLoaderStateCopyWithImpl; +@useResult +$Res call({ + List items, int page, int totalPages, Option failureOption, bool isFetching, bool isLoadingMore, bool hasLoaded +}); + + + + +} +/// @nodoc +class _$VoucherRedemptionLoaderStateCopyWithImpl<$Res> + implements $VoucherRedemptionLoaderStateCopyWith<$Res> { + _$VoucherRedemptionLoaderStateCopyWithImpl(this._self, this._then); + + final VoucherRedemptionLoaderState _self; + final $Res Function(VoucherRedemptionLoaderState) _then; + +/// Create a copy of VoucherRedemptionLoaderState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? page = null,Object? totalPages = null,Object? failureOption = null,Object? isFetching = null,Object? isLoadingMore = null,Object? hasLoaded = null,}) { + return _then(VoucherRedemptionLoaderState( +items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable +as List,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable +as int,totalPages: null == totalPages ? _self.totalPages : totalPages // ignore: cast_nullable_to_non_nullable +as int,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable +as Option,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable +as bool,hasLoaded: null == hasLoaded ? _self.hasLoaded : hasLoaded // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [VoucherRedemptionLoaderState]. +extension VoucherRedemptionLoaderStatePatterns on VoucherRedemptionLoaderState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherRedemptionLoaderState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _VoucherRedemptionLoaderState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _VoucherRedemptionLoaderState value) $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemptionLoaderState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherRedemptionLoaderState value)? $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemptionLoaderState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List items, int page, int totalPages, Option failureOption, bool isFetching, bool isLoadingMore, bool hasLoaded)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _VoucherRedemptionLoaderState() when $default != null: +return $default(_that.items,_that.page,_that.totalPages,_that.failureOption,_that.isFetching,_that.isLoadingMore,_that.hasLoaded);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List items, int page, int totalPages, Option failureOption, bool isFetching, bool isLoadingMore, bool hasLoaded) $default,) {final _that = this; +switch (_that) { +case _VoucherRedemptionLoaderState(): +return $default(_that.items,_that.page,_that.totalPages,_that.failureOption,_that.isFetching,_that.isLoadingMore,_that.hasLoaded);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List items, int page, int totalPages, Option failureOption, bool isFetching, bool isLoadingMore, bool hasLoaded)? $default,) {final _that = this; +switch (_that) { +case _VoucherRedemptionLoaderState() when $default != null: +return $default(_that.items,_that.page,_that.totalPages,_that.failureOption,_that.isFetching,_that.isLoadingMore,_that.hasLoaded);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _VoucherRedemptionLoaderState extends VoucherRedemptionLoaderState { + const _VoucherRedemptionLoaderState({required List items, required this.page, required this.totalPages, required this.failureOption, this.isFetching = false, this.isLoadingMore = false, this.hasLoaded = false}): _items = items,super._(); + + + final List _items; +@override List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); +} + +@override final int page; +@override final int totalPages; +@override final Option failureOption; +@override@JsonKey() final bool isFetching; +@override@JsonKey() final bool isLoadingMore; +@override@JsonKey() final bool hasLoaded; + +/// Create a copy of VoucherRedemptionLoaderState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherRedemptionLoaderStateCopyWith<_VoucherRedemptionLoaderState> get copyWith => __$VoucherRedemptionLoaderStateCopyWithImpl<_VoucherRedemptionLoaderState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherRedemptionLoaderState&&const DeepCollectionEquality().equals(other.items, _items)&&(identical(other.page, page) || other.page == page)&&(identical(other.totalPages, totalPages) || other.totalPages == totalPages)&&(identical(other.failureOption, failureOption) || other.failureOption == failureOption)&&(identical(other.isFetching, isFetching) || other.isFetching == isFetching)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&(identical(other.hasLoaded, hasLoaded) || other.hasLoaded == hasLoaded)); +} + + +@override +int get hashCode { + return Object.hash(runtimeType,const DeepCollectionEquality().hash(_items),page,totalPages,failureOption,isFetching,isLoadingMore,hasLoaded); +} + +@override +String toString() { + return 'VoucherRedemptionLoaderState(items: $items, page: $page, totalPages: $totalPages, failureOption: $failureOption, isFetching: $isFetching, isLoadingMore: $isLoadingMore, hasLoaded: $hasLoaded)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherRedemptionLoaderStateCopyWith<$Res> implements $VoucherRedemptionLoaderStateCopyWith<$Res> { + factory _$VoucherRedemptionLoaderStateCopyWith(_VoucherRedemptionLoaderState value, $Res Function(_VoucherRedemptionLoaderState) _then) = __$VoucherRedemptionLoaderStateCopyWithImpl; +@override @useResult +$Res call({ + List items, int page, int totalPages, Option failureOption, bool isFetching, bool isLoadingMore, bool hasLoaded +}); + + + + +} +/// @nodoc +class __$VoucherRedemptionLoaderStateCopyWithImpl<$Res> + implements _$VoucherRedemptionLoaderStateCopyWith<$Res> { + __$VoucherRedemptionLoaderStateCopyWithImpl(this._self, this._then); + + final _VoucherRedemptionLoaderState _self; + final $Res Function(_VoucherRedemptionLoaderState) _then; + +/// Create a copy of VoucherRedemptionLoaderState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? page = null,Object? totalPages = null,Object? failureOption = null,Object? isFetching = null,Object? isLoadingMore = null,Object? hasLoaded = null,}) { + return _then(_VoucherRedemptionLoaderState( +items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable +as List,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable +as int,totalPages: null == totalPages ? _self.totalPages : totalPages // ignore: cast_nullable_to_non_nullable +as int,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable +as Option,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable +as bool,hasLoaded: null == hasLoaded ? _self.hasLoaded : hasLoaded // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +// dart format on diff --git a/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_event.dart b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_event.dart new file mode 100644 index 0000000..5db2a2b --- /dev/null +++ b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_event.dart @@ -0,0 +1,10 @@ +part of 'voucher_redemption_loader_bloc.dart'; + +@freezed +abstract class VoucherRedemptionLoaderEvent + with _$VoucherRedemptionLoaderEvent { + /// Muat ulang dari halaman pertama. + const factory VoucherRedemptionLoaderEvent.refreshed() = _Refreshed; + + const factory VoucherRedemptionLoaderEvent.loadedMore() = _LoadedMore; +} diff --git a/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_state.dart b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_state.dart new file mode 100644 index 0000000..186ffb3 --- /dev/null +++ b/lib/application/voucher/voucher_redemption_loader/voucher_redemption_loader_state.dart @@ -0,0 +1,27 @@ +part of 'voucher_redemption_loader_bloc.dart'; + +@freezed +abstract class VoucherRedemptionLoaderState + with _$VoucherRedemptionLoaderState { + const VoucherRedemptionLoaderState._(); + + const factory VoucherRedemptionLoaderState({ + required List items, + required int page, + required int totalPages, + required Option failureOption, + @Default(false) bool isFetching, + @Default(false) bool isLoadingMore, + @Default(false) bool hasLoaded, + }) = _VoucherRedemptionLoaderState; + + factory VoucherRedemptionLoaderState.initial() => + VoucherRedemptionLoaderState( + items: const [], + page: 0, + totalPages: 0, + failureOption: none(), + ); + + bool get hasMore => page < totalPages; +} diff --git a/lib/application/wallet/payment_code_form/payment_code_form_bloc.dart b/lib/application/wallet/payment_code_form/payment_code_form_bloc.dart deleted file mode 100644 index 8542c2b..0000000 --- a/lib/application/wallet/payment_code_form/payment_code_form_bloc.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:dartz/dartz.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:injectable/injectable.dart'; - -import '../../../domain/wallet/wallet.dart'; - -part 'payment_code_form_event.dart'; -part 'payment_code_form_state.dart'; -part 'payment_code_form_bloc.freezed.dart'; - -@injectable -class PaymentCodeFormBloc - extends Bloc { - final IWalletRepository _repository; - - PaymentCodeFormBloc(this._repository) - : super(PaymentCodeFormState.initial()) { - on(_onPaymentCodeFormEvent); - } - - Future _onPaymentCodeFormEvent( - PaymentCodeFormEvent event, - Emitter emit, - ) { - return event.map( - submitted: (e) async { - emit(state.copyWith(isSubmitting: true, failureOption: none())); - - final result = await _repository.createPaymentCode(pin: e.pin); - - emit( - result - .fold( - (f) => state.copyWith(failureOption: optionOf(f)), - (code) => state.copyWith(paymentCode: code), - ) - .copyWith(isSubmitting: false), - ); - }, - expired: (e) async => emit(state.copyWith(paymentCode: null)), - ); - } -} diff --git a/lib/application/wallet/payment_code_form/payment_code_form_bloc.freezed.dart b/lib/application/wallet/payment_code_form/payment_code_form_bloc.freezed.dart deleted file mode 100644 index 48eb1ef..0000000 --- a/lib/application/wallet/payment_code_form/payment_code_form_bloc.freezed.dart +++ /dev/null @@ -1,575 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'payment_code_form_bloc.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// GENERATED CODE - DO NOT MODIFY BY HAND -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$PaymentCodeFormEvent { - - - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentCodeFormEvent); -} - - -@override -int get hashCode => runtimeType.hashCode; - -@override -String toString() { - return 'PaymentCodeFormEvent()'; -} - - -} - -/// @nodoc -class $PaymentCodeFormEventCopyWith<$Res> { -$PaymentCodeFormEventCopyWith(PaymentCodeFormEvent _, $Res Function(PaymentCodeFormEvent) __); -} - - -/// Adds pattern-matching-related methods to [PaymentCodeFormEvent]. -extension PaymentCodeFormEventPatterns on PaymentCodeFormEvent { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap({TResult Function( _Submitted value)? submitted,TResult Function( _Expired value)? expired,required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _Submitted() when submitted != null: -return submitted(_that);case _Expired() when expired != null: -return expired(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map({required TResult Function( _Submitted value) submitted,required TResult Function( _Expired value) expired,}){ -final _that = this; -switch (_that) { -case _Submitted(): -return submitted(_that);case _Expired(): -return expired(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull({TResult? Function( _Submitted value)? submitted,TResult? Function( _Expired value)? expired,}){ -final _that = this; -switch (_that) { -case _Submitted() when submitted != null: -return submitted(_that);case _Expired() when expired != null: -return expired(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen({TResult Function( String pin)? submitted,TResult Function()? expired,required TResult orElse(),}) {final _that = this; -switch (_that) { -case _Submitted() when submitted != null: -return submitted(_that.pin);case _Expired() when expired != null: -return expired();case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when({required TResult Function( String pin) submitted,required TResult Function() expired,}) {final _that = this; -switch (_that) { -case _Submitted(): -return submitted(_that.pin);case _Expired(): -return expired();case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull({TResult? Function( String pin)? submitted,TResult? Function()? expired,}) {final _that = this; -switch (_that) { -case _Submitted() when submitted != null: -return submitted(_that.pin);case _Expired() when expired != null: -return expired();case _: - return null; - -} -} - -} - -/// @nodoc - - -class _Submitted implements PaymentCodeFormEvent { - const _Submitted(this.pin); - - - final String pin; - -/// Create a copy of PaymentCodeFormEvent -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$SubmittedCopyWith<_Submitted> get copyWith => __$SubmittedCopyWithImpl<_Submitted>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Submitted&&(identical(other.pin, pin) || other.pin == pin)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,pin); -} - -@override -String toString() { - return 'PaymentCodeFormEvent.submitted(pin: $pin)'; -} - - -} - -/// @nodoc -abstract mixin class _$SubmittedCopyWith<$Res> implements $PaymentCodeFormEventCopyWith<$Res> { - factory _$SubmittedCopyWith(_Submitted value, $Res Function(_Submitted) _then) = __$SubmittedCopyWithImpl; -@useResult -$Res call({ - String pin -}); - - - - -} -/// @nodoc -class __$SubmittedCopyWithImpl<$Res> - implements _$SubmittedCopyWith<$Res> { - __$SubmittedCopyWithImpl(this._self, this._then); - - final _Submitted _self; - final $Res Function(_Submitted) _then; - -/// Create a copy of PaymentCodeFormEvent -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') $Res call({Object? pin = null,}) { - return _then(_Submitted( -null == pin ? _self.pin : pin // ignore: cast_nullable_to_non_nullable -as String, - )); -} - - -} - -/// @nodoc - - -class _Expired implements PaymentCodeFormEvent { - const _Expired(); - - - - - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Expired); -} - - -@override -int get hashCode => runtimeType.hashCode; - -@override -String toString() { - return 'PaymentCodeFormEvent.expired()'; -} - - -} - - - - -/// @nodoc -mixin _$PaymentCodeFormState { - - PaymentCode? get paymentCode; Option get failureOption; bool get isSubmitting; -/// Create a copy of PaymentCodeFormState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$PaymentCodeFormStateCopyWith get copyWith => _$PaymentCodeFormStateCopyWithImpl(this as PaymentCodeFormState, _$identity); - - - -@override -bool operator ==(Object other) { - final _this = this as PaymentCodeFormState; - return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentCodeFormState&&(identical(other.paymentCode, _this.paymentCode) || other.paymentCode == _this.paymentCode)&&(identical(other.failureOption, _this.failureOption) || other.failureOption == _this.failureOption)&&(identical(other.isSubmitting, _this.isSubmitting) || other.isSubmitting == _this.isSubmitting)); -} - - -@override -int get hashCode { - final _this = this as PaymentCodeFormState; - return Object.hash(runtimeType,_this.paymentCode,_this.failureOption,_this.isSubmitting); -} - -@override -String toString() { - final _this = this as PaymentCodeFormState; - return 'PaymentCodeFormState(paymentCode: ${_this.paymentCode}, failureOption: ${_this.failureOption}, isSubmitting: ${_this.isSubmitting})'; -} - - -} - -/// @nodoc -abstract mixin class $PaymentCodeFormStateCopyWith<$Res> { - factory $PaymentCodeFormStateCopyWith(PaymentCodeFormState value, $Res Function(PaymentCodeFormState) _then) = _$PaymentCodeFormStateCopyWithImpl; -@useResult -$Res call({ - PaymentCode? paymentCode, Option failureOption, bool isSubmitting -}); - - -$PaymentCodeCopyWith<$Res>? get paymentCode; - -} -/// @nodoc -class _$PaymentCodeFormStateCopyWithImpl<$Res> - implements $PaymentCodeFormStateCopyWith<$Res> { - _$PaymentCodeFormStateCopyWithImpl(this._self, this._then); - - final PaymentCodeFormState _self; - final $Res Function(PaymentCodeFormState) _then; - -/// Create a copy of PaymentCodeFormState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? paymentCode = freezed,Object? failureOption = null,Object? isSubmitting = null,}) { - return _then(PaymentCodeFormState( -paymentCode: freezed == paymentCode ? _self.paymentCode : paymentCode // ignore: cast_nullable_to_non_nullable -as PaymentCode?,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable -as Option,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable -as bool, - )); -} -/// Create a copy of PaymentCodeFormState -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$PaymentCodeCopyWith<$Res>? get paymentCode { - if (_self.paymentCode == null) { - return null; - } - - return $PaymentCodeCopyWith<$Res>(_self.paymentCode!, (value) { - return _then(_self.copyWith(paymentCode: value)); - }); -} -} - - -/// Adds pattern-matching-related methods to [PaymentCodeFormState]. -extension PaymentCodeFormStatePatterns on PaymentCodeFormState { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _PaymentCodeFormState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _PaymentCodeFormState() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _PaymentCodeFormState value) $default,){ -final _that = this; -switch (_that) { -case _PaymentCodeFormState(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PaymentCodeFormState value)? $default,){ -final _that = this; -switch (_that) { -case _PaymentCodeFormState() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( PaymentCode? paymentCode, Option failureOption, bool isSubmitting)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _PaymentCodeFormState() when $default != null: -return $default(_that.paymentCode,_that.failureOption,_that.isSubmitting);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( PaymentCode? paymentCode, Option failureOption, bool isSubmitting) $default,) {final _that = this; -switch (_that) { -case _PaymentCodeFormState(): -return $default(_that.paymentCode,_that.failureOption,_that.isSubmitting);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( PaymentCode? paymentCode, Option failureOption, bool isSubmitting)? $default,) {final _that = this; -switch (_that) { -case _PaymentCodeFormState() when $default != null: -return $default(_that.paymentCode,_that.failureOption,_that.isSubmitting);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _PaymentCodeFormState implements PaymentCodeFormState { - const _PaymentCodeFormState({this.paymentCode, required this.failureOption, this.isSubmitting = false}); - - -@override final PaymentCode? paymentCode; -@override final Option failureOption; -@override@JsonKey() final bool isSubmitting; - -/// Create a copy of PaymentCodeFormState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$PaymentCodeFormStateCopyWith<_PaymentCodeFormState> get copyWith => __$PaymentCodeFormStateCopyWithImpl<_PaymentCodeFormState>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentCodeFormState&&(identical(other.paymentCode, paymentCode) || other.paymentCode == paymentCode)&&(identical(other.failureOption, failureOption) || other.failureOption == failureOption)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,paymentCode,failureOption,isSubmitting); -} - -@override -String toString() { - return 'PaymentCodeFormState(paymentCode: $paymentCode, failureOption: $failureOption, isSubmitting: $isSubmitting)'; -} - - -} - -/// @nodoc -abstract mixin class _$PaymentCodeFormStateCopyWith<$Res> implements $PaymentCodeFormStateCopyWith<$Res> { - factory _$PaymentCodeFormStateCopyWith(_PaymentCodeFormState value, $Res Function(_PaymentCodeFormState) _then) = __$PaymentCodeFormStateCopyWithImpl; -@override @useResult -$Res call({ - PaymentCode? paymentCode, Option failureOption, bool isSubmitting -}); - - -@override $PaymentCodeCopyWith<$Res>? get paymentCode; - -} -/// @nodoc -class __$PaymentCodeFormStateCopyWithImpl<$Res> - implements _$PaymentCodeFormStateCopyWith<$Res> { - __$PaymentCodeFormStateCopyWithImpl(this._self, this._then); - - final _PaymentCodeFormState _self; - final $Res Function(_PaymentCodeFormState) _then; - -/// Create a copy of PaymentCodeFormState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? paymentCode = freezed,Object? failureOption = null,Object? isSubmitting = null,}) { - return _then(_PaymentCodeFormState( -paymentCode: freezed == paymentCode ? _self.paymentCode : paymentCode // ignore: cast_nullable_to_non_nullable -as PaymentCode?,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable -as Option,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable -as bool, - )); -} - -/// Create a copy of PaymentCodeFormState -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$PaymentCodeCopyWith<$Res>? get paymentCode { - if (_self.paymentCode == null) { - return null; - } - - return $PaymentCodeCopyWith<$Res>(_self.paymentCode!, (value) { - return _then(_self.copyWith(paymentCode: value)); - }); -} -} - -// dart format on diff --git a/lib/application/wallet/payment_code_form/payment_code_form_event.dart b/lib/application/wallet/payment_code_form/payment_code_form_event.dart deleted file mode 100644 index eb056f3..0000000 --- a/lib/application/wallet/payment_code_form/payment_code_form_event.dart +++ /dev/null @@ -1,9 +0,0 @@ -part of 'payment_code_form_bloc.dart'; - -@freezed -abstract class PaymentCodeFormEvent with _$PaymentCodeFormEvent { - /// [pin] hanya diteruskan ke API, tidak disimpan di state. - const factory PaymentCodeFormEvent.submitted(String pin) = _Submitted; - - const factory PaymentCodeFormEvent.expired() = _Expired; -} diff --git a/lib/application/wallet/payment_code_form/payment_code_form_state.dart b/lib/application/wallet/payment_code_form/payment_code_form_state.dart deleted file mode 100644 index 8414425..0000000 --- a/lib/application/wallet/payment_code_form/payment_code_form_state.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of 'payment_code_form_bloc.dart'; - -@freezed -abstract class PaymentCodeFormState with _$PaymentCodeFormState { - const factory PaymentCodeFormState({ - PaymentCode? paymentCode, - required Option failureOption, - @Default(false) bool isSubmitting, - }) = _PaymentCodeFormState; - - factory PaymentCodeFormState.initial() => - PaymentCodeFormState(failureOption: none()); -} diff --git a/lib/application/wallet/wallet_history_loader/wallet_history_loader_bloc.dart b/lib/application/wallet/wallet_history_loader/wallet_history_loader_bloc.dart index 2a04ba3..467a93a 100644 --- a/lib/application/wallet/wallet_history_loader/wallet_history_loader_bloc.dart +++ b/lib/application/wallet/wallet_history_loader/wallet_history_loader_bloc.dart @@ -27,7 +27,16 @@ class WalletHistoryLoaderBloc ) { return event.map( currencyChanged: (e) async { - emit(state.copyWith(currency: e.currency)); + // Tipe yang tidak dimiliki mata uang ini (mis. Main game di tab + // EnakPoint) dibuang dari filter. + emit( + state.copyWith( + currency: e.currency, + types: state.types + .where((type) => type.currencies.contains(e.currency)) + .toList(), + ), + ); await _fetchFirstPage(emit); }, filterChanged: (e) async { diff --git a/lib/application/wallet/wallet_loader/wallet_loader_event.dart b/lib/application/wallet/wallet_loader/wallet_loader_event.dart index ff00d33..0a80c86 100644 --- a/lib/application/wallet/wallet_loader/wallet_loader_event.dart +++ b/lib/application/wallet/wallet_loader/wallet_loader_event.dart @@ -4,7 +4,8 @@ part of 'wallet_loader_bloc.dart'; abstract class WalletLoaderEvent with _$WalletLoaderEvent { const factory WalletLoaderEvent.fetched() = _Fetched; - /// Saldo EnakCoin terbaru dari hasil main game (`coins_remaining`). + /// Saldo EnakCoin terbaru yang dikirim game lewat bridge + /// (`balance_changed`). const factory WalletLoaderEvent.coinBalanceUpdated(int coinBalance) = _CoinBalanceUpdated; diff --git a/lib/common/api/interceptors/unauthorized_interceptor.dart b/lib/common/api/interceptors/unauthorized_interceptor.dart index 646ef40..64a5bbb 100644 --- a/lib/common/api/interceptors/unauthorized_interceptor.dart +++ b/lib/common/api/interceptors/unauthorized_interceptor.dart @@ -1,40 +1,55 @@ import 'dart:developer'; -import 'package:auto_route/auto_route.dart'; import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../../injection.dart'; +import '../../../presentation/router/app_router.dart'; import '../../../presentation/router/app_router.gr.dart'; import '../../constant/local_storage_key.dart'; -import '../errors/unauthorized_error.dart'; +/// Token login ditolak server: HTTP `401`, atau `304` dengan `entity` +/// `auth_handler` (§2.2). Belum ada refresh token, jadi customer harus login +/// ulang. +/// +/// Bukan `403`: `PIN_NOT_SET` dan `TRANSFER_BLOCKED` juga memakai `403`. +bool isTokenRejected(Response response) { + if (response.statusCode == 401) return true; + + final body = response.data; + if (body is! Map) return false; + final errors = body['errors']; + if (errors is! List || errors.isEmpty || errors.first is! Map) return false; + + final first = errors.first as Map; + return first['code']?.toString() == '304' && + first['entity'] == 'auth_handler'; +} + +/// Menghapus sesi dan membuka halaman login bila token yang dipakai request +/// ditolak. `validateStatus` menerima semua status, jadi pengecekan ada di +/// [onResponse], bukan `onError`. class UnauthorizedInterceptor extends Interceptor { - static final GlobalKey navigatorKey = - GlobalKey(); @override - void onError(DioException err, ErrorInterceptorHandler handler) async { - if (err.response?.statusCode == 401 || - err.response?.statusCode == 403 || - err.response?.statusCode == 419) { - await _handleTokenExpired(); - return super.onError(UnauthorizedError(err, null), handler); + void onResponse(Response response, ResponseInterceptorHandler handler) async { + try { + if (isTokenRejected(response)) await _handleTokenRejected(response); + } finally { + handler.next(response); } - super.onError(err, handler); } - Future _handleTokenExpired() async { - // Clear stored token - final prefs = await SharedPreferences.getInstance(); + Future _handleTokenRejected(Response response) async { + final prefs = getIt(); + final token = prefs.getString(LocalStorageKey.token); + // Hanya token yang sedang dipakai: abaikan request tanpa login dan jawaban + // basi dari token lama setelah customer login lagi. + final sentAuthorization = response.requestOptions.headers['Authorization']; + if (token == null || sentAuthorization != 'Bearer $token') return; + + log('token rejected', name: 'UnauthorizedInterceptor'); await prefs.remove(LocalStorageKey.token); await prefs.remove(LocalStorageKey.user); - await prefs.clear(); // Optional: clear all user data - log('handleTokenExpired'); - // Navigate to login page - final context = navigatorKey.currentContext; - if (context != null) { - // Option 1: Navigate and remove all previous routes - context.router.replaceAll([LoginRoute()]); - } + getIt().replaceAll([LoginRoute()]); } } diff --git a/lib/common/constant/app_constant.dart b/lib/common/constant/app_constant.dart index a53d17d..47f2207 100644 --- a/lib/common/constant/app_constant.dart +++ b/lib/common/constant/app_constant.dart @@ -8,7 +8,8 @@ class AppConstant { static const String poinName = "EnakPoint"; static const String gameUrl = "https://game.enaklo.com/"; - /// `theme-color` game.enaklo.com. + /// Latar webview EnakGame selama halaman game dimuat (`theme-color` + /// game.enaklo.com). static const Color gameBackgroundColor = Color(0xFF09090B); } diff --git a/lib/common/painter/wheel_painter.dart b/lib/common/painter/wheel_painter.dart deleted file mode 100644 index 2415de4..0000000 --- a/lib/common/painter/wheel_painter.dart +++ /dev/null @@ -1,148 +0,0 @@ -// wheel_painter.dart - Fixed implementation with consistent positioning -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import '../../domain/game/game.dart'; - -class WheelPainter extends CustomPainter { - final List gamePrizes; - final Color Function(GamePrize prize, int index) getPrizeColor; - - WheelPainter({required this.gamePrizes, required this.getPrizeColor}); - - @override - void paint(Canvas canvas, Size size) { - if (gamePrizes.isEmpty) return; - - final center = Offset(size.width / 2, size.height / 2); - final radius = size.width / 2; - final sectionAngle = 2 * math.pi / gamePrizes.length; - - // Draw outer white border - final outerBorderPaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.fill; - canvas.drawCircle(center, radius, outerBorderPaint); - - // Draw blue border ring - final blueBorderPaint = Paint() - ..color = const Color(0xFF3B82F6) - ..style = PaintingStyle.fill; - canvas.drawCircle(center, radius - 8, blueBorderPaint); - - // Draw inner white circle - final innerWhitePaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.fill; - canvas.drawCircle(center, radius - 20, innerWhitePaint); - - // Draw sections - KONSISTEN dengan logic spin - for (int i = 0; i < gamePrizes.length; i++) { - final prize = gamePrizes[i]; - // Section 0 di top (-π/2), section 1 di kanan atas, dst (clockwise) - final startAngle = (-math.pi / 2) + (i * sectionAngle); - - // Section background - final sectionPaint = Paint() - ..color = getPrizeColor(prize, i) - ..style = PaintingStyle.fill; - - canvas.drawArc( - Rect.fromCircle(center: center, radius: radius - 22), - startAngle, - sectionAngle, - true, - sectionPaint, - ); - - // Draw prize text - final textAngle = startAngle + sectionAngle / 2; - final textPosition = Offset( - center.dx + (radius - 80) * math.cos(textAngle), - center.dy + (radius - 80) * math.sin(textAngle), - ); - - canvas.save(); - canvas.translate(textPosition.dx, textPosition.dy); - canvas.rotate(textAngle + math.pi / 2); - - final textPainter = TextPainter( - text: TextSpan( - text: prize.name, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - textDirection: TextDirection.ltr, - textAlign: TextAlign.center, - ); - textPainter.layout(maxWidth: 100); - textPainter.paint( - canvas, - Offset(-textPainter.width / 2, -textPainter.height / 2), - ); - canvas.restore(); - - // DEBUG: Draw section number - final numberPosition = Offset( - center.dx + (radius - 50) * math.cos(textAngle), - center.dy + (radius - 50) * math.sin(textAngle), - ); - canvas.drawCircle(numberPosition, 15, Paint()..color = Colors.white); - final numberPainter = TextPainter( - text: TextSpan( - text: i.toString(), - style: const TextStyle( - color: Colors.black, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - textDirection: TextDirection.ltr, - ); - numberPainter.layout(); - numberPainter.paint( - canvas, - Offset( - numberPosition.dx - numberPainter.width / 2, - numberPosition.dy - numberPainter.height / 2, - ), - ); - } - - // Draw white dots - final dotPaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.fill; - for (int i = 0; i < 24; i++) { - final dotAngle = (2 * math.pi / 24) * i; - final dotPosition = Offset( - center.dx + (radius - 14) * math.cos(dotAngle), - center.dy + (radius - 14) * math.sin(dotAngle), - ); - canvas.drawCircle(dotPosition, 3, dotPaint); - } - - // Draw section dividers - final dividerPaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.stroke - ..strokeWidth = 2; - for (int i = 0; i < gamePrizes.length; i++) { - final angle = (-math.pi / 2) + (i * sectionAngle); - final lineStart = Offset( - center.dx + (radius - 110) * math.cos(angle), - center.dy + (radius - 110) * math.sin(angle), - ); - final lineEnd = Offset( - center.dx + (radius - 22) * math.cos(angle), - center.dy + (radius - 22) * math.sin(angle), - ); - canvas.drawLine(lineStart, lineEnd, dividerPaint); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => true; -} diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index d05545f..71876e5 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -6,15 +6,10 @@ class ApiPath { static String login = '/api/v1/customer-auth/login'; static String resend = '/api/v1/customer-auth/resend-otp'; - // Marketing - static String ferrisWheel = '/api/v1/customer/ferris-wheel'; - static String spin = '/api/v1/customer/spin'; - // Wallet static String wallet = '/api/v1/customer/wallet'; static String walletTransactions = '/api/v1/customer/wallet/transactions'; static String walletExpiring = '/api/v1/customer/wallet/expiring'; - static String walletPaymentCode = '/api/v1/customer/wallet/payment-code'; static String walletExchangePreview = '/api/v1/customer/wallet/exchange/preview'; static String walletExchange = '/api/v1/customer/wallet/exchange'; @@ -32,6 +27,12 @@ class ApiPath { static String pinOtp = '/api/v1/customer/pin/otp'; static String pinReset = '/api/v1/customer/pin/reset'; + // Voucher + static String vouchers = '/api/v1/customer/vouchers'; + static String voucherRedemptions = '/api/v1/customer/vouchers/redemptions'; + static String voucherRedeem(String voucherId) => + '/api/v1/customer/vouchers/$voucherId/redeem'; + // Device (push notification) static String devices = '/api/v1/customer/devices'; } diff --git a/lib/domain/game/entity/game_entity.dart b/lib/domain/game/entity/game_entity.dart deleted file mode 100644 index 6b93475..0000000 --- a/lib/domain/game/entity/game_entity.dart +++ /dev/null @@ -1,35 +0,0 @@ -part of '../game.dart'; - -@freezed -abstract class Game with _$Game { - const Game._(); - - const factory Game({ - required String id, - required String name, - required String type, - required bool isActive, - required Map metadata, - required List prizes, - required String createdAt, - required String updatedAt, - }) = _Game; - - factory Game.empty() => const Game( - id: '', - name: '', - type: '', - isActive: false, - metadata: {}, - prizes: [], - createdAt: '', - updatedAt: '', - ); - - /// Biaya sekali main dalam EnakCoin (`metadata.coin_cost`), default 1. - int get coinCost { - final value = metadata['coin_cost']; - final cost = value is num ? value.toInt() : int.tryParse('$value'); - return cost == null || cost <= 0 ? 1 : cost; - } -} diff --git a/lib/domain/game/entity/game_prize_entity.dart b/lib/domain/game/entity/game_prize_entity.dart deleted file mode 100644 index 92d2d97..0000000 --- a/lib/domain/game/entity/game_prize_entity.dart +++ /dev/null @@ -1,22 +0,0 @@ -part of '../game.dart'; - -@freezed -abstract class GamePrize with _$GamePrize { - const factory GamePrize({ - required String id, - required String gameId, - required String name, - required Map metadata, - required String createdAt, - required String updatedAt, - }) = _GamePrize; - - factory GamePrize.empty() => const GamePrize( - id: '', - gameId: '', - name: '', - metadata: {}, - createdAt: '', - updatedAt: '', - ); -} diff --git a/lib/domain/game/entity/spin_result_entity.dart b/lib/domain/game/entity/spin_result_entity.dart deleted file mode 100644 index 29cc09f..0000000 --- a/lib/domain/game/entity/spin_result_entity.dart +++ /dev/null @@ -1,12 +0,0 @@ -part of '../game.dart'; - -@freezed -abstract class SpinResult with _$SpinResult { - const factory SpinResult({ - required String gamePlayId, - required int coinsUsed, - required String prizeId, - required String prizeName, - required int coinsRemaining, - }) = _SpinResult; -} diff --git a/lib/domain/game/failures/game_failure.dart b/lib/domain/game/failures/game_failure.dart deleted file mode 100644 index ea61756..0000000 --- a/lib/domain/game/failures/game_failure.dart +++ /dev/null @@ -1,9 +0,0 @@ -part of '../game.dart'; - -@freezed -sealed class GameFailure with _$GameFailure { - const factory GameFailure.serverError(ApiFailure failure) = _ServerError; - const factory GameFailure.unexpectedError() = _UnexpectedError; - const factory GameFailure.dynamicErrorMessage(String erroMessage) = - _DynamicErrorMessage; -} diff --git a/lib/domain/game/game.dart b/lib/domain/game/game.dart deleted file mode 100644 index 146ac6d..0000000 --- a/lib/domain/game/game.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:dartz/dartz.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -import '../../common/api/api_failure.dart'; - -part 'game.freezed.dart'; - -part 'entity/game_entity.dart'; -part 'entity/game_prize_entity.dart'; -part 'entity/spin_result_entity.dart'; -part 'failures/game_failure.dart'; -part 'repositories/i_game_repository.dart'; diff --git a/lib/domain/game/game.freezed.dart b/lib/domain/game/game.freezed.dart deleted file mode 100644 index 1a478d7..0000000 --- a/lib/domain/game/game.freezed.dart +++ /dev/null @@ -1,1216 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'game.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// GENERATED CODE - DO NOT MODIFY BY HAND -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$Game { - - String get id; String get name; String get type; bool get isActive; Map get metadata; List get prizes; String get createdAt; String get updatedAt; -/// Create a copy of Game -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$GameCopyWith get copyWith => _$GameCopyWithImpl(this as Game, _$identity); - - - -@override -bool operator ==(Object other) { - final _this = this as Game; - return identical(this, other) || (other.runtimeType == runtimeType&&other is Game&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.type, _this.type) || other.type == _this.type)&&(identical(other.isActive, _this.isActive) || other.isActive == _this.isActive)&&const DeepCollectionEquality().equals(other.metadata, _this.metadata)&&const DeepCollectionEquality().equals(other.prizes, _this.prizes)&&(identical(other.createdAt, _this.createdAt) || other.createdAt == _this.createdAt)&&(identical(other.updatedAt, _this.updatedAt) || other.updatedAt == _this.updatedAt)); -} - - -@override -int get hashCode { - final _this = this as Game; - return Object.hash(runtimeType,_this.id,_this.name,_this.type,_this.isActive,const DeepCollectionEquality().hash(_this.metadata),const DeepCollectionEquality().hash(_this.prizes),_this.createdAt,_this.updatedAt); -} - -@override -String toString() { - final _this = this as Game; - return 'Game(id: ${_this.id}, name: ${_this.name}, type: ${_this.type}, isActive: ${_this.isActive}, metadata: ${_this.metadata}, prizes: ${_this.prizes}, createdAt: ${_this.createdAt}, updatedAt: ${_this.updatedAt})'; -} - - -} - -/// @nodoc -abstract mixin class $GameCopyWith<$Res> { - factory $GameCopyWith(Game value, $Res Function(Game) _then) = _$GameCopyWithImpl; -@useResult -$Res call({ - String id, String name, String type, bool isActive, Map metadata, List prizes, String createdAt, String updatedAt -}); - - - - -} -/// @nodoc -class _$GameCopyWithImpl<$Res> - implements $GameCopyWith<$Res> { - _$GameCopyWithImpl(this._self, this._then); - - final Game _self; - final $Res Function(Game) _then; - -/// Create a copy of Game -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? type = null,Object? isActive = null,Object? metadata = null,Object? prizes = null,Object? createdAt = null,Object? updatedAt = null,}) { - return _then(Game( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable -as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable -as bool,metadata: null == metadata ? _self.metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map,prizes: null == prizes ? _self.prizes : prizes // ignore: cast_nullable_to_non_nullable -as List,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String, - )); -} - -} - - -/// Adds pattern-matching-related methods to [Game]. -extension GamePatterns on Game { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _Game value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _Game() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _Game value) $default,){ -final _that = this; -switch (_that) { -case _Game(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Game value)? $default,){ -final _that = this; -switch (_that) { -case _Game() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String type, bool isActive, Map metadata, List prizes, String createdAt, String updatedAt)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _Game() when $default != null: -return $default(_that.id,_that.name,_that.type,_that.isActive,_that.metadata,_that.prizes,_that.createdAt,_that.updatedAt);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( String id, String name, String type, bool isActive, Map metadata, List prizes, String createdAt, String updatedAt) $default,) {final _that = this; -switch (_that) { -case _Game(): -return $default(_that.id,_that.name,_that.type,_that.isActive,_that.metadata,_that.prizes,_that.createdAt,_that.updatedAt);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String type, bool isActive, Map metadata, List prizes, String createdAt, String updatedAt)? $default,) {final _that = this; -switch (_that) { -case _Game() when $default != null: -return $default(_that.id,_that.name,_that.type,_that.isActive,_that.metadata,_that.prizes,_that.createdAt,_that.updatedAt);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _Game extends Game { - const _Game({required this.id, required this.name, required this.type, required this.isActive, required Map metadata, required List prizes, required this.createdAt, required this.updatedAt}): _metadata = metadata,_prizes = prizes,super._(); - - -@override final String id; -@override final String name; -@override final String type; -@override final bool isActive; - final Map _metadata; -@override Map get metadata { - if (_metadata is EqualUnmodifiableMapView) return _metadata; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_metadata); -} - - final List _prizes; -@override List get prizes { - if (_prizes is EqualUnmodifiableListView) return _prizes; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_prizes); -} - -@override final String createdAt; -@override final String updatedAt; - -/// Create a copy of Game -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$GameCopyWith<_Game> get copyWith => __$GameCopyWithImpl<_Game>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Game&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&const DeepCollectionEquality().equals(other.metadata, _metadata)&&const DeepCollectionEquality().equals(other.prizes, _prizes)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,id,name,type,isActive,const DeepCollectionEquality().hash(_metadata),const DeepCollectionEquality().hash(_prizes),createdAt,updatedAt); -} - -@override -String toString() { - return 'Game(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)'; -} - - -} - -/// @nodoc -abstract mixin class _$GameCopyWith<$Res> implements $GameCopyWith<$Res> { - factory _$GameCopyWith(_Game value, $Res Function(_Game) _then) = __$GameCopyWithImpl; -@override @useResult -$Res call({ - String id, String name, String type, bool isActive, Map metadata, List prizes, String createdAt, String updatedAt -}); - - - - -} -/// @nodoc -class __$GameCopyWithImpl<$Res> - implements _$GameCopyWith<$Res> { - __$GameCopyWithImpl(this._self, this._then); - - final _Game _self; - final $Res Function(_Game) _then; - -/// Create a copy of Game -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? type = null,Object? isActive = null,Object? metadata = null,Object? prizes = null,Object? createdAt = null,Object? updatedAt = null,}) { - return _then(_Game( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable -as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable -as bool,metadata: null == metadata ? _self._metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map,prizes: null == prizes ? _self._prizes : prizes // ignore: cast_nullable_to_non_nullable -as List,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String, - )); -} - - -} - -/// @nodoc -mixin _$GamePrize { - - String get id; String get gameId; String get name; Map get metadata; String get createdAt; String get updatedAt; -/// Create a copy of GamePrize -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$GamePrizeCopyWith get copyWith => _$GamePrizeCopyWithImpl(this as GamePrize, _$identity); - - - -@override -bool operator ==(Object other) { - final _this = this as GamePrize; - return identical(this, other) || (other.runtimeType == runtimeType&&other is GamePrize&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.gameId, _this.gameId) || other.gameId == _this.gameId)&&(identical(other.name, _this.name) || other.name == _this.name)&&const DeepCollectionEquality().equals(other.metadata, _this.metadata)&&(identical(other.createdAt, _this.createdAt) || other.createdAt == _this.createdAt)&&(identical(other.updatedAt, _this.updatedAt) || other.updatedAt == _this.updatedAt)); -} - - -@override -int get hashCode { - final _this = this as GamePrize; - return Object.hash(runtimeType,_this.id,_this.gameId,_this.name,const DeepCollectionEquality().hash(_this.metadata),_this.createdAt,_this.updatedAt); -} - -@override -String toString() { - final _this = this as GamePrize; - return 'GamePrize(id: ${_this.id}, gameId: ${_this.gameId}, name: ${_this.name}, metadata: ${_this.metadata}, createdAt: ${_this.createdAt}, updatedAt: ${_this.updatedAt})'; -} - - -} - -/// @nodoc -abstract mixin class $GamePrizeCopyWith<$Res> { - factory $GamePrizeCopyWith(GamePrize value, $Res Function(GamePrize) _then) = _$GamePrizeCopyWithImpl; -@useResult -$Res call({ - String id, String gameId, String name, Map metadata, String createdAt, String updatedAt -}); - - - - -} -/// @nodoc -class _$GamePrizeCopyWithImpl<$Res> - implements $GamePrizeCopyWith<$Res> { - _$GamePrizeCopyWithImpl(this._self, this._then); - - final GamePrize _self; - final $Res Function(GamePrize) _then; - -/// Create a copy of GamePrize -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? gameId = null,Object? name = null,Object? metadata = null,Object? createdAt = null,Object? updatedAt = null,}) { - return _then(GamePrize( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,gameId: null == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String,metadata: null == metadata ? _self.metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String, - )); -} - -} - - -/// Adds pattern-matching-related methods to [GamePrize]. -extension GamePrizePatterns on GamePrize { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _GamePrize value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _GamePrize() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _GamePrize value) $default,){ -final _that = this; -switch (_that) { -case _GamePrize(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GamePrize value)? $default,){ -final _that = this; -switch (_that) { -case _GamePrize() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String gameId, String name, Map metadata, String createdAt, String updatedAt)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _GamePrize() when $default != null: -return $default(_that.id,_that.gameId,_that.name,_that.metadata,_that.createdAt,_that.updatedAt);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( String id, String gameId, String name, Map metadata, String createdAt, String updatedAt) $default,) {final _that = this; -switch (_that) { -case _GamePrize(): -return $default(_that.id,_that.gameId,_that.name,_that.metadata,_that.createdAt,_that.updatedAt);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String gameId, String name, Map metadata, String createdAt, String updatedAt)? $default,) {final _that = this; -switch (_that) { -case _GamePrize() when $default != null: -return $default(_that.id,_that.gameId,_that.name,_that.metadata,_that.createdAt,_that.updatedAt);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _GamePrize implements GamePrize { - const _GamePrize({required this.id, required this.gameId, required this.name, required Map metadata, required this.createdAt, required this.updatedAt}): _metadata = metadata; - - -@override final String id; -@override final String gameId; -@override final String name; - final Map _metadata; -@override Map get metadata { - if (_metadata is EqualUnmodifiableMapView) return _metadata; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_metadata); -} - -@override final String createdAt; -@override final String updatedAt; - -/// Create a copy of GamePrize -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$GamePrizeCopyWith<_GamePrize> get copyWith => __$GamePrizeCopyWithImpl<_GamePrize>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _GamePrize&&(identical(other.id, id) || other.id == id)&&(identical(other.gameId, gameId) || other.gameId == gameId)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.metadata, _metadata)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,id,gameId,name,const DeepCollectionEquality().hash(_metadata),createdAt,updatedAt); -} - -@override -String toString() { - return 'GamePrize(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; -} - - -} - -/// @nodoc -abstract mixin class _$GamePrizeCopyWith<$Res> implements $GamePrizeCopyWith<$Res> { - factory _$GamePrizeCopyWith(_GamePrize value, $Res Function(_GamePrize) _then) = __$GamePrizeCopyWithImpl; -@override @useResult -$Res call({ - String id, String gameId, String name, Map metadata, String createdAt, String updatedAt -}); - - - - -} -/// @nodoc -class __$GamePrizeCopyWithImpl<$Res> - implements _$GamePrizeCopyWith<$Res> { - __$GamePrizeCopyWithImpl(this._self, this._then); - - final _GamePrize _self; - final $Res Function(_GamePrize) _then; - -/// Create a copy of GamePrize -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? gameId = null,Object? name = null,Object? metadata = null,Object? createdAt = null,Object? updatedAt = null,}) { - return _then(_GamePrize( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,gameId: null == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String,metadata: null == metadata ? _self._metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String, - )); -} - - -} - -/// @nodoc -mixin _$SpinResult { - - String get gamePlayId; int get coinsUsed; String get prizeId; String get prizeName; int get coinsRemaining; -/// Create a copy of SpinResult -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$SpinResultCopyWith get copyWith => _$SpinResultCopyWithImpl(this as SpinResult, _$identity); - - - -@override -bool operator ==(Object other) { - final _this = this as SpinResult; - return identical(this, other) || (other.runtimeType == runtimeType&&other is SpinResult&&(identical(other.gamePlayId, _this.gamePlayId) || other.gamePlayId == _this.gamePlayId)&&(identical(other.coinsUsed, _this.coinsUsed) || other.coinsUsed == _this.coinsUsed)&&(identical(other.prizeId, _this.prizeId) || other.prizeId == _this.prizeId)&&(identical(other.prizeName, _this.prizeName) || other.prizeName == _this.prizeName)&&(identical(other.coinsRemaining, _this.coinsRemaining) || other.coinsRemaining == _this.coinsRemaining)); -} - - -@override -int get hashCode { - final _this = this as SpinResult; - return Object.hash(runtimeType,_this.gamePlayId,_this.coinsUsed,_this.prizeId,_this.prizeName,_this.coinsRemaining); -} - -@override -String toString() { - final _this = this as SpinResult; - return 'SpinResult(gamePlayId: ${_this.gamePlayId}, coinsUsed: ${_this.coinsUsed}, prizeId: ${_this.prizeId}, prizeName: ${_this.prizeName}, coinsRemaining: ${_this.coinsRemaining})'; -} - - -} - -/// @nodoc -abstract mixin class $SpinResultCopyWith<$Res> { - factory $SpinResultCopyWith(SpinResult value, $Res Function(SpinResult) _then) = _$SpinResultCopyWithImpl; -@useResult -$Res call({ - String gamePlayId, int coinsUsed, String prizeId, String prizeName, int coinsRemaining -}); - - - - -} -/// @nodoc -class _$SpinResultCopyWithImpl<$Res> - implements $SpinResultCopyWith<$Res> { - _$SpinResultCopyWithImpl(this._self, this._then); - - final SpinResult _self; - final $Res Function(SpinResult) _then; - -/// Create a copy of SpinResult -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? gamePlayId = null,Object? coinsUsed = null,Object? prizeId = null,Object? prizeName = null,Object? coinsRemaining = null,}) { - return _then(SpinResult( -gamePlayId: null == gamePlayId ? _self.gamePlayId : gamePlayId // ignore: cast_nullable_to_non_nullable -as String,coinsUsed: null == coinsUsed ? _self.coinsUsed : coinsUsed // ignore: cast_nullable_to_non_nullable -as int,prizeId: null == prizeId ? _self.prizeId : prizeId // ignore: cast_nullable_to_non_nullable -as String,prizeName: null == prizeName ? _self.prizeName : prizeName // ignore: cast_nullable_to_non_nullable -as String,coinsRemaining: null == coinsRemaining ? _self.coinsRemaining : coinsRemaining // ignore: cast_nullable_to_non_nullable -as int, - )); -} - -} - - -/// Adds pattern-matching-related methods to [SpinResult]. -extension SpinResultPatterns on SpinResult { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _SpinResult value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _SpinResult() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _SpinResult value) $default,){ -final _that = this; -switch (_that) { -case _SpinResult(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SpinResult value)? $default,){ -final _that = this; -switch (_that) { -case _SpinResult() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( String gamePlayId, int coinsUsed, String prizeId, String prizeName, int coinsRemaining)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _SpinResult() when $default != null: -return $default(_that.gamePlayId,_that.coinsUsed,_that.prizeId,_that.prizeName,_that.coinsRemaining);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( String gamePlayId, int coinsUsed, String prizeId, String prizeName, int coinsRemaining) $default,) {final _that = this; -switch (_that) { -case _SpinResult(): -return $default(_that.gamePlayId,_that.coinsUsed,_that.prizeId,_that.prizeName,_that.coinsRemaining);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String gamePlayId, int coinsUsed, String prizeId, String prizeName, int coinsRemaining)? $default,) {final _that = this; -switch (_that) { -case _SpinResult() when $default != null: -return $default(_that.gamePlayId,_that.coinsUsed,_that.prizeId,_that.prizeName,_that.coinsRemaining);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _SpinResult implements SpinResult { - const _SpinResult({required this.gamePlayId, required this.coinsUsed, required this.prizeId, required this.prizeName, required this.coinsRemaining}); - - -@override final String gamePlayId; -@override final int coinsUsed; -@override final String prizeId; -@override final String prizeName; -@override final int coinsRemaining; - -/// Create a copy of SpinResult -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$SpinResultCopyWith<_SpinResult> get copyWith => __$SpinResultCopyWithImpl<_SpinResult>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _SpinResult&&(identical(other.gamePlayId, gamePlayId) || other.gamePlayId == gamePlayId)&&(identical(other.coinsUsed, coinsUsed) || other.coinsUsed == coinsUsed)&&(identical(other.prizeId, prizeId) || other.prizeId == prizeId)&&(identical(other.prizeName, prizeName) || other.prizeName == prizeName)&&(identical(other.coinsRemaining, coinsRemaining) || other.coinsRemaining == coinsRemaining)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,gamePlayId,coinsUsed,prizeId,prizeName,coinsRemaining); -} - -@override -String toString() { - return 'SpinResult(gamePlayId: $gamePlayId, coinsUsed: $coinsUsed, prizeId: $prizeId, prizeName: $prizeName, coinsRemaining: $coinsRemaining)'; -} - - -} - -/// @nodoc -abstract mixin class _$SpinResultCopyWith<$Res> implements $SpinResultCopyWith<$Res> { - factory _$SpinResultCopyWith(_SpinResult value, $Res Function(_SpinResult) _then) = __$SpinResultCopyWithImpl; -@override @useResult -$Res call({ - String gamePlayId, int coinsUsed, String prizeId, String prizeName, int coinsRemaining -}); - - - - -} -/// @nodoc -class __$SpinResultCopyWithImpl<$Res> - implements _$SpinResultCopyWith<$Res> { - __$SpinResultCopyWithImpl(this._self, this._then); - - final _SpinResult _self; - final $Res Function(_SpinResult) _then; - -/// Create a copy of SpinResult -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? gamePlayId = null,Object? coinsUsed = null,Object? prizeId = null,Object? prizeName = null,Object? coinsRemaining = null,}) { - return _then(_SpinResult( -gamePlayId: null == gamePlayId ? _self.gamePlayId : gamePlayId // ignore: cast_nullable_to_non_nullable -as String,coinsUsed: null == coinsUsed ? _self.coinsUsed : coinsUsed // ignore: cast_nullable_to_non_nullable -as int,prizeId: null == prizeId ? _self.prizeId : prizeId // ignore: cast_nullable_to_non_nullable -as String,prizeName: null == prizeName ? _self.prizeName : prizeName // ignore: cast_nullable_to_non_nullable -as String,coinsRemaining: null == coinsRemaining ? _self.coinsRemaining : coinsRemaining // ignore: cast_nullable_to_non_nullable -as int, - )); -} - - -} - -/// @nodoc -mixin _$GameFailure { - - - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is GameFailure); -} - - -@override -int get hashCode => runtimeType.hashCode; - -@override -String toString() { - return 'GameFailure()'; -} - - -} - -/// @nodoc -class $GameFailureCopyWith<$Res> { -$GameFailureCopyWith(GameFailure _, $Res Function(GameFailure) __); -} - - -/// Adds pattern-matching-related methods to [GameFailure]. -extension GameFailurePatterns on GameFailure { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap({TResult Function( _ServerError value)? serverError,TResult Function( _UnexpectedError value)? unexpectedError,TResult Function( _DynamicErrorMessage value)? dynamicErrorMessage,required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _ServerError() when serverError != null: -return serverError(_that);case _UnexpectedError() when unexpectedError != null: -return unexpectedError(_that);case _DynamicErrorMessage() when dynamicErrorMessage != null: -return dynamicErrorMessage(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map({required TResult Function( _ServerError value) serverError,required TResult Function( _UnexpectedError value) unexpectedError,required TResult Function( _DynamicErrorMessage value) dynamicErrorMessage,}){ -final _that = this; -switch (_that) { -case _ServerError(): -return serverError(_that);case _UnexpectedError(): -return unexpectedError(_that);case _DynamicErrorMessage(): -return dynamicErrorMessage(_that);} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull({TResult? Function( _ServerError value)? serverError,TResult? Function( _UnexpectedError value)? unexpectedError,TResult? Function( _DynamicErrorMessage value)? dynamicErrorMessage,}){ -final _that = this; -switch (_that) { -case _ServerError() when serverError != null: -return serverError(_that);case _UnexpectedError() when unexpectedError != null: -return unexpectedError(_that);case _DynamicErrorMessage() when dynamicErrorMessage != null: -return dynamicErrorMessage(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen({TResult Function( ApiFailure failure)? serverError,TResult Function()? unexpectedError,TResult Function( String erroMessage)? dynamicErrorMessage,required TResult orElse(),}) {final _that = this; -switch (_that) { -case _ServerError() when serverError != null: -return serverError(_that.failure);case _UnexpectedError() when unexpectedError != null: -return unexpectedError();case _DynamicErrorMessage() when dynamicErrorMessage != null: -return dynamicErrorMessage(_that.erroMessage);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when({required TResult Function( ApiFailure failure) serverError,required TResult Function() unexpectedError,required TResult Function( String erroMessage) dynamicErrorMessage,}) {final _that = this; -switch (_that) { -case _ServerError(): -return serverError(_that.failure);case _UnexpectedError(): -return unexpectedError();case _DynamicErrorMessage(): -return dynamicErrorMessage(_that.erroMessage);} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull({TResult? Function( ApiFailure failure)? serverError,TResult? Function()? unexpectedError,TResult? Function( String erroMessage)? dynamicErrorMessage,}) {final _that = this; -switch (_that) { -case _ServerError() when serverError != null: -return serverError(_that.failure);case _UnexpectedError() when unexpectedError != null: -return unexpectedError();case _DynamicErrorMessage() when dynamicErrorMessage != null: -return dynamicErrorMessage(_that.erroMessage);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _ServerError implements GameFailure { - const _ServerError(this.failure); - - - final ApiFailure failure; - -/// Create a copy of GameFailure -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$ServerErrorCopyWith<_ServerError> get copyWith => __$ServerErrorCopyWithImpl<_ServerError>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerError&&(identical(other.failure, failure) || other.failure == failure)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,failure); -} - -@override -String toString() { - return 'GameFailure.serverError(failure: $failure)'; -} - - -} - -/// @nodoc -abstract mixin class _$ServerErrorCopyWith<$Res> implements $GameFailureCopyWith<$Res> { - factory _$ServerErrorCopyWith(_ServerError value, $Res Function(_ServerError) _then) = __$ServerErrorCopyWithImpl; -@useResult -$Res call({ - ApiFailure failure -}); - - -$ApiFailureCopyWith<$Res> get failure; - -} -/// @nodoc -class __$ServerErrorCopyWithImpl<$Res> - implements _$ServerErrorCopyWith<$Res> { - __$ServerErrorCopyWithImpl(this._self, this._then); - - final _ServerError _self; - final $Res Function(_ServerError) _then; - -/// Create a copy of GameFailure -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') $Res call({Object? failure = null,}) { - return _then(_ServerError( -null == failure ? _self.failure : failure // ignore: cast_nullable_to_non_nullable -as ApiFailure, - )); -} - -/// Create a copy of GameFailure -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$ApiFailureCopyWith<$Res> get failure { - - return $ApiFailureCopyWith<$Res>(_self.failure, (value) { - return _then(_self.copyWith(failure: value)); - }); -} -} - -/// @nodoc - - -class _UnexpectedError implements GameFailure { - const _UnexpectedError(); - - - - - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _UnexpectedError); -} - - -@override -int get hashCode => runtimeType.hashCode; - -@override -String toString() { - return 'GameFailure.unexpectedError()'; -} - - -} - - - - -/// @nodoc - - -class _DynamicErrorMessage implements GameFailure { - const _DynamicErrorMessage(this.erroMessage); - - - final String erroMessage; - -/// Create a copy of GameFailure -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$DynamicErrorMessageCopyWith<_DynamicErrorMessage> get copyWith => __$DynamicErrorMessageCopyWithImpl<_DynamicErrorMessage>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _DynamicErrorMessage&&(identical(other.erroMessage, erroMessage) || other.erroMessage == erroMessage)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,erroMessage); -} - -@override -String toString() { - return 'GameFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; -} - - -} - -/// @nodoc -abstract mixin class _$DynamicErrorMessageCopyWith<$Res> implements $GameFailureCopyWith<$Res> { - factory _$DynamicErrorMessageCopyWith(_DynamicErrorMessage value, $Res Function(_DynamicErrorMessage) _then) = __$DynamicErrorMessageCopyWithImpl; -@useResult -$Res call({ - String erroMessage -}); - - - - -} -/// @nodoc -class __$DynamicErrorMessageCopyWithImpl<$Res> - implements _$DynamicErrorMessageCopyWith<$Res> { - __$DynamicErrorMessageCopyWithImpl(this._self, this._then); - - final _DynamicErrorMessage _self; - final $Res Function(_DynamicErrorMessage) _then; - -/// Create a copy of GameFailure -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') $Res call({Object? erroMessage = null,}) { - return _then(_DynamicErrorMessage( -null == erroMessage ? _self.erroMessage : erroMessage // ignore: cast_nullable_to_non_nullable -as String, - )); -} - - -} - -// dart format on diff --git a/lib/domain/game/repositories/i_game_repository.dart b/lib/domain/game/repositories/i_game_repository.dart deleted file mode 100644 index c40cba3..0000000 --- a/lib/domain/game/repositories/i_game_repository.dart +++ /dev/null @@ -1,8 +0,0 @@ -part of '../game.dart'; - -abstract class IGameRepository { - Future> ferrisWheel(); - - /// Main satu kali; memotong EnakCoin sebesar biaya game. Tanpa PIN. - Future> spin({required String gameId}); -} diff --git a/lib/domain/wallet/entities/customer_order_entity.dart b/lib/domain/wallet/entities/customer_order_entity.dart index 7901b05..d51b859 100644 --- a/lib/domain/wallet/entities/customer_order_entity.dart +++ b/lib/domain/wallet/entities/customer_order_entity.dart @@ -71,18 +71,11 @@ abstract class CustomerOrderItem with _$CustomerOrderItem { @freezed abstract class CustomerOrderPayment with _$CustomerOrderPayment { - const CustomerOrderPayment._(); - const factory CustomerOrderPayment({ required String id, required String methodName, required String methodType, required int amount, required int refundAmount, - - /// Hanya pada pembayaran EnakPoint. - int? pointsUsed, }) = _CustomerOrderPayment; - - bool get isPoint => methodType == 'point'; } diff --git a/lib/domain/wallet/entities/outlet_entity.dart b/lib/domain/wallet/entities/outlet_entity.dart index 7966ee0..a8544ca 100644 --- a/lib/domain/wallet/entities/outlet_entity.dart +++ b/lib/domain/wallet/entities/outlet_entity.dart @@ -8,9 +8,6 @@ abstract class Outlet with _$Outlet { required String name, String? address, - /// Kasir di outlet ini menerima pembayaran EnakPoint. - required bool acceptsPointPayment, - /// Belanja di outlet ini memberi EnakPoint. required bool earnsPoints, diff --git a/lib/domain/wallet/entities/payment_code_entity.dart b/lib/domain/wallet/entities/payment_code_entity.dart deleted file mode 100644 index b293ed3..0000000 --- a/lib/domain/wallet/entities/payment_code_entity.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of '../wallet.dart'; - -@freezed -abstract class PaymentCode with _$PaymentCode { - const factory PaymentCode({ - required String code, - required String qrPayload, - required DateTime expiresAt, - }) = _PaymentCode; -} diff --git a/lib/domain/wallet/entities/voucher_entity.dart b/lib/domain/wallet/entities/voucher_entity.dart new file mode 100644 index 0000000..6a620d0 --- /dev/null +++ b/lib/domain/wallet/entities/voucher_entity.dart @@ -0,0 +1,72 @@ +part of '../wallet.dart'; + +/// Voucher di katalog yang bisa ditukar dengan EnakPoint sekarang (§9.1). +@freezed +abstract class Voucher with _$Voucher { + const Voucher._(); + + const factory Voucher({ + required String id, + required String name, + String? description, + String? imageUrl, + required VoucherType type, + + /// Nilai voucher dalam rupiah. + required int faceValue, + + /// EnakPoint yang dipotong saat ditukar. + required int pointCost, + + /// Batas tukar per customer; `null` berarti tanpa batas. + int? maxPerCustomer, + DateTime? validUntil, + + /// Sisa stok; `null` berarti stok tidak dihitung. + int? available, + }) = _Voucher; + + bool get isOutOfStock => available != null && available! <= 0; +} + +/// Voucher hasil tukar milik customer (§9.3). +@freezed +abstract class VoucherRedemption with _$VoucherRedemption { + const factory VoucherRedemption({ + required String id, + required String voucherId, + required String voucherName, + String? voucherImageUrl, + required VoucherType voucherType, + required VoucherRedemptionStatus status, + required int faceValue, + required int pointCost, + + /// `null` bila voucher tidak memakai kode; customer menunjukkan layar + /// voucher ke kasir. + String? code, + DateTime? codeExpiresAt, + DateTime? completedAt, + DateTime? createdAt, + }) = _VoucherRedemption; +} + +@freezed +abstract class VoucherRedemptionPage with _$VoucherRedemptionPage { + const factory VoucherRedemptionPage({ + required List items, + required int page, + required int totalPages, + }) = _VoucherRedemptionPage; +} + +@freezed +abstract class VoucherRedeemResult with _$VoucherRedeemResult { + const factory VoucherRedeemResult({ + required VoucherRedemption redemption, + + /// Saldo EnakPoint setelah ditukar. + required int pointBalance, + required bool replayed, + }) = _VoucherRedeemResult; +} diff --git a/lib/domain/wallet/entities/wallet_enum.dart b/lib/domain/wallet/entities/wallet_enum.dart index cdfbd16..1faeffa 100644 --- a/lib/domain/wallet/entities/wallet_enum.dart +++ b/lib/domain/wallet/entities/wallet_enum.dart @@ -13,29 +13,47 @@ enum WalletCurrency { value == coin.apiValue ? coin : point; } -enum WalletTransactionType { - earn('EARN', 'Dari belanja'), - earnReversal('EARN_REVERSAL', 'Dibatalkan (order di-void/refund)'), - payment('PAYMENT', 'Bayar pesanan'), - paymentRefund('PAYMENT_REFUND', 'Pengembalian pembayaran'), - exchangeOut('EXCHANGE_OUT', 'Ditukar ke EnakPoint'), - exchangeIn('EXCHANGE_IN', 'Hasil tukar EnakCoin'), - transferOut('TRANSFER_OUT', 'Transfer keluar'), - transferIn('TRANSFER_IN', 'Transfer masuk'), - gameSpend('GAME_SPEND', 'Main game'), - expire('EXPIRE', 'Kedaluwarsa'), - adjustment('ADJUSTMENT', 'Koreksi'), - migration('MIGRATION', 'Saldo awal'), - unknown('', 'Transaksi'); +const _bothCurrencies = [WalletCurrency.point, WalletCurrency.coin]; - const WalletTransactionType(this.apiValue, this.label); +enum WalletTransactionType { + earn('EARN', 'Dari belanja', _bothCurrencies), + earnReversal( + 'EARN_REVERSAL', + 'Dibatalkan (order di-void/refund)', + _bothCurrencies, + ), + exchangeOut('EXCHANGE_OUT', 'Ditukar ke EnakPoint', [WalletCurrency.coin]), + exchangeIn('EXCHANGE_IN', 'Hasil tukar EnakCoin', [WalletCurrency.point]), + transferOut('TRANSFER_OUT', 'Transfer keluar', _bothCurrencies), + transferIn('TRANSFER_IN', 'Transfer masuk', _bothCurrencies), + gameSpend('GAME_SPEND', 'Main game', [WalletCurrency.coin]), + gameSpendRefund('GAME_SPEND_REFUND', 'Biaya main dikembalikan', [ + WalletCurrency.coin, + ]), + gameReward('GAME_REWARD', 'Hadiah game', [WalletCurrency.coin]), + rewardRedeem('REWARD_REDEEM', 'Ditukar ke voucher', [WalletCurrency.point]), + rewardRedeemRefund('REWARD_REDEEM_REFUND', 'Penukaran voucher dibatalkan', [ + WalletCurrency.point, + ]), + expire('EXPIRE', 'Kedaluwarsa', _bothCurrencies), + adjustment('ADJUSTMENT', 'Koreksi', _bothCurrencies), + migration('MIGRATION', 'Saldo awal', _bothCurrencies), + + /// Tipe baru dari backend yang belum dikenal app: tampilkan `description` + /// tanpa label. + unknown('', '', []); + + const WalletTransactionType(this.apiValue, this.label, this.currencies); final String apiValue; final String label; - /// Tipe yang bisa dipilih di filter riwayat. - static List get filterable => - values.where((e) => e != unknown).toList(); + /// Mata uang yang bisa memiliki tipe ini. + final List currencies; + + /// Tipe yang bisa dipilih di filter riwayat untuk tab [currency]. + static List filterableFor(WalletCurrency currency) => + values.where((e) => e.currencies.contains(currency)).toList(); static WalletTransactionType fromApi(String? value) => values.firstWhere((e) => e.apiValue == value, orElse: () => unknown); @@ -49,3 +67,36 @@ enum PinOtpPurpose { final String apiValue; } + +enum VoucherType { + fixedValue('FIXED_VALUE'), + percentage('PERCENTAGE'), + freeItem('FREE_ITEM'), + merchantBenefit('MERCHANT_BENEFIT'), + unknown(''); + + const VoucherType(this.apiValue); + + final String apiValue; + + static VoucherType fromApi(String? value) => + values.firstWhere((e) => e.apiValue == value, orElse: () => unknown); +} + +enum VoucherRedemptionStatus { + completed('COMPLETED'), + + /// Sedang diproses penyedia luar; EnakPoint sudah terpotong. + pending('PENDING'), + + /// Gagal; EnakPoint dikembalikan otomatis (`REWARD_REDEEM_REFUND`). + failed('FAILED'), + unknown(''); + + const VoucherRedemptionStatus(this.apiValue); + + final String apiValue; + + static VoucherRedemptionStatus fromApi(String? value) => + values.firstWhere((e) => e.apiValue == value, orElse: () => unknown); +} diff --git a/lib/domain/wallet/repositories/i_order_repository.dart b/lib/domain/wallet/repositories/i_order_repository.dart index 0c35814..458b553 100644 --- a/lib/domain/wallet/repositories/i_order_repository.dart +++ b/lib/domain/wallet/repositories/i_order_repository.dart @@ -1,7 +1,7 @@ part of '../wallet.dart'; /// Riwayat order customer. App hanya melihat order; tidak membuat atau -/// membayarnya (§7). +/// membayarnya; EnakPoint tidak bisa dipakai membayar (§1). abstract class IOrderRepository { Future> getOrders({ required int page, diff --git a/lib/domain/wallet/repositories/i_voucher_repository.dart b/lib/domain/wallet/repositories/i_voucher_repository.dart new file mode 100644 index 0000000..fdf6b95 --- /dev/null +++ b/lib/domain/wallet/repositories/i_voucher_repository.dart @@ -0,0 +1,19 @@ +part of '../wallet.dart'; + +/// Tukar EnakPoint ke voucher (§9). +abstract class IVoucherRepository { + /// Voucher yang aktif, dalam masa berlaku, dan masih ada stoknya. + Future>> getVouchers(); + + Future> redeem({ + required String voucherId, + required String pin, + required String idempotencyKey, + }); + + /// Penukaran milik customer, terbaru di atas. + Future> getRedemptions({ + required int page, + int limit = 20, + }); +} diff --git a/lib/domain/wallet/repositories/i_wallet_repository.dart b/lib/domain/wallet/repositories/i_wallet_repository.dart index eae6db9..9263bc0 100644 --- a/lib/domain/wallet/repositories/i_wallet_repository.dart +++ b/lib/domain/wallet/repositories/i_wallet_repository.dart @@ -14,10 +14,6 @@ abstract class IWalletRepository { Future> getExpiring(); - Future> createPaymentCode({ - required String pin, - }); - Future> previewExchange({ required int coins, }); diff --git a/lib/domain/wallet/wallet.dart b/lib/domain/wallet/wallet.dart index f057f43..660433d 100644 --- a/lib/domain/wallet/wallet.dart +++ b/lib/domain/wallet/wallet.dart @@ -9,15 +9,16 @@ part 'entities/wallet_enum.dart'; part 'entities/wallet_summary_entity.dart'; part 'entities/wallet_transaction_entity.dart'; part 'entities/wallet_expiring_entity.dart'; -part 'entities/payment_code_entity.dart'; part 'entities/exchange_entity.dart'; part 'entities/transfer_entity.dart'; part 'entities/pin_entity.dart'; part 'entities/outlet_entity.dart'; part 'entities/customer_order_entity.dart'; +part 'entities/voucher_entity.dart'; part 'failures/wallet_failure.dart'; part 'repositories/i_wallet_repository.dart'; part 'repositories/i_pin_repository.dart'; part 'repositories/i_device_repository.dart'; part 'repositories/i_outlet_repository.dart'; part 'repositories/i_order_repository.dart'; +part 'repositories/i_voucher_repository.dart'; diff --git a/lib/domain/wallet/wallet.freezed.dart b/lib/domain/wallet/wallet.freezed.dart index dff592b..6b38781 100644 --- a/lib/domain/wallet/wallet.freezed.dart +++ b/lib/domain/wallet/wallet.freezed.dart @@ -1459,276 +1459,6 @@ as List, } -} - -/// @nodoc -mixin _$PaymentCode { - - String get code; String get qrPayload; DateTime get expiresAt; -/// Create a copy of PaymentCode -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$PaymentCodeCopyWith get copyWith => _$PaymentCodeCopyWithImpl(this as PaymentCode, _$identity); - - - -@override -bool operator ==(Object other) { - final _this = this as PaymentCode; - return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentCode&&(identical(other.code, _this.code) || other.code == _this.code)&&(identical(other.qrPayload, _this.qrPayload) || other.qrPayload == _this.qrPayload)&&(identical(other.expiresAt, _this.expiresAt) || other.expiresAt == _this.expiresAt)); -} - - -@override -int get hashCode { - final _this = this as PaymentCode; - return Object.hash(runtimeType,_this.code,_this.qrPayload,_this.expiresAt); -} - -@override -String toString() { - final _this = this as PaymentCode; - return 'PaymentCode(code: ${_this.code}, qrPayload: ${_this.qrPayload}, expiresAt: ${_this.expiresAt})'; -} - - -} - -/// @nodoc -abstract mixin class $PaymentCodeCopyWith<$Res> { - factory $PaymentCodeCopyWith(PaymentCode value, $Res Function(PaymentCode) _then) = _$PaymentCodeCopyWithImpl; -@useResult -$Res call({ - String code, String qrPayload, DateTime expiresAt -}); - - - - -} -/// @nodoc -class _$PaymentCodeCopyWithImpl<$Res> - implements $PaymentCodeCopyWith<$Res> { - _$PaymentCodeCopyWithImpl(this._self, this._then); - - final PaymentCode _self; - final $Res Function(PaymentCode) _then; - -/// Create a copy of PaymentCode -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? code = null,Object? qrPayload = null,Object? expiresAt = null,}) { - return _then(PaymentCode( -code: null == code ? _self.code : code // ignore: cast_nullable_to_non_nullable -as String,qrPayload: null == qrPayload ? _self.qrPayload : qrPayload // ignore: cast_nullable_to_non_nullable -as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable -as DateTime, - )); -} - -} - - -/// Adds pattern-matching-related methods to [PaymentCode]. -extension PaymentCodePatterns on PaymentCode { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _PaymentCode value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _PaymentCode() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _PaymentCode value) $default,){ -final _that = this; -switch (_that) { -case _PaymentCode(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PaymentCode value)? $default,){ -final _that = this; -switch (_that) { -case _PaymentCode() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( String code, String qrPayload, DateTime expiresAt)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _PaymentCode() when $default != null: -return $default(_that.code,_that.qrPayload,_that.expiresAt);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( String code, String qrPayload, DateTime expiresAt) $default,) {final _that = this; -switch (_that) { -case _PaymentCode(): -return $default(_that.code,_that.qrPayload,_that.expiresAt);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String code, String qrPayload, DateTime expiresAt)? $default,) {final _that = this; -switch (_that) { -case _PaymentCode() when $default != null: -return $default(_that.code,_that.qrPayload,_that.expiresAt);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _PaymentCode implements PaymentCode { - const _PaymentCode({required this.code, required this.qrPayload, required this.expiresAt}); - - -@override final String code; -@override final String qrPayload; -@override final DateTime expiresAt; - -/// Create a copy of PaymentCode -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$PaymentCodeCopyWith<_PaymentCode> get copyWith => __$PaymentCodeCopyWithImpl<_PaymentCode>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentCode&&(identical(other.code, code) || other.code == code)&&(identical(other.qrPayload, qrPayload) || other.qrPayload == qrPayload)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); -} - - -@override -int get hashCode { - return Object.hash(runtimeType,code,qrPayload,expiresAt); -} - -@override -String toString() { - return 'PaymentCode(code: $code, qrPayload: $qrPayload, expiresAt: $expiresAt)'; -} - - -} - -/// @nodoc -abstract mixin class _$PaymentCodeCopyWith<$Res> implements $PaymentCodeCopyWith<$Res> { - factory _$PaymentCodeCopyWith(_PaymentCode value, $Res Function(_PaymentCode) _then) = __$PaymentCodeCopyWithImpl; -@override @useResult -$Res call({ - String code, String qrPayload, DateTime expiresAt -}); - - - - -} -/// @nodoc -class __$PaymentCodeCopyWithImpl<$Res> - implements _$PaymentCodeCopyWith<$Res> { - __$PaymentCodeCopyWithImpl(this._self, this._then); - - final _PaymentCode _self; - final $Res Function(_PaymentCode) _then; - -/// Create a copy of PaymentCode -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? code = null,Object? qrPayload = null,Object? expiresAt = null,}) { - return _then(_PaymentCode( -code: null == code ? _self.code : code // ignore: cast_nullable_to_non_nullable -as String,qrPayload: null == qrPayload ? _self.qrPayload : qrPayload // ignore: cast_nullable_to_non_nullable -as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable -as DateTime, - )); -} - - } /// @nodoc @@ -3678,8 +3408,7 @@ as DateTime?, /// @nodoc mixin _$Outlet { - String get id; String get name; String? get address;/// Kasir di outlet ini menerima pembayaran EnakPoint. - bool get acceptsPointPayment;/// Belanja di outlet ini memberi EnakPoint. + String get id; String get name; String? get address;/// Belanja di outlet ini memberi EnakPoint. bool get earnsPoints;/// Belanja di outlet ini memberi EnakCoin. bool get earnsCoins; /// Create a copy of Outlet @@ -3693,20 +3422,20 @@ $OutletCopyWith get copyWith => _$OutletCopyWithImpl(this as Out @override bool operator ==(Object other) { final _this = this as Outlet; - return identical(this, other) || (other.runtimeType == runtimeType&&other is Outlet&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.address, _this.address) || other.address == _this.address)&&(identical(other.acceptsPointPayment, _this.acceptsPointPayment) || other.acceptsPointPayment == _this.acceptsPointPayment)&&(identical(other.earnsPoints, _this.earnsPoints) || other.earnsPoints == _this.earnsPoints)&&(identical(other.earnsCoins, _this.earnsCoins) || other.earnsCoins == _this.earnsCoins)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is Outlet&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.address, _this.address) || other.address == _this.address)&&(identical(other.earnsPoints, _this.earnsPoints) || other.earnsPoints == _this.earnsPoints)&&(identical(other.earnsCoins, _this.earnsCoins) || other.earnsCoins == _this.earnsCoins)); } @override int get hashCode { final _this = this as Outlet; - return Object.hash(runtimeType,_this.id,_this.name,_this.address,_this.acceptsPointPayment,_this.earnsPoints,_this.earnsCoins); + return Object.hash(runtimeType,_this.id,_this.name,_this.address,_this.earnsPoints,_this.earnsCoins); } @override String toString() { final _this = this as Outlet; - return 'Outlet(id: ${_this.id}, name: ${_this.name}, address: ${_this.address}, acceptsPointPayment: ${_this.acceptsPointPayment}, earnsPoints: ${_this.earnsPoints}, earnsCoins: ${_this.earnsCoins})'; + return 'Outlet(id: ${_this.id}, name: ${_this.name}, address: ${_this.address}, earnsPoints: ${_this.earnsPoints}, earnsCoins: ${_this.earnsCoins})'; } @@ -3717,7 +3446,7 @@ abstract mixin class $OutletCopyWith<$Res> { factory $OutletCopyWith(Outlet value, $Res Function(Outlet) _then) = _$OutletCopyWithImpl; @useResult $Res call({ - String id, String name, String? address, bool acceptsPointPayment, bool earnsPoints, bool earnsCoins + String id, String name, String? address, bool earnsPoints, bool earnsCoins }); @@ -3734,13 +3463,12 @@ class _$OutletCopyWithImpl<$Res> /// Create a copy of Outlet /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? address = freezed,Object? acceptsPointPayment = null,Object? earnsPoints = null,Object? earnsCoins = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? address = freezed,Object? earnsPoints = null,Object? earnsCoins = null,}) { return _then(Outlet( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable -as String?,acceptsPointPayment: null == acceptsPointPayment ? _self.acceptsPointPayment : acceptsPointPayment // ignore: cast_nullable_to_non_nullable -as bool,earnsPoints: null == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable +as String?,earnsPoints: null == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable as bool,earnsCoins: null == earnsCoins ? _self.earnsCoins : earnsCoins // ignore: cast_nullable_to_non_nullable as bool, )); @@ -3827,10 +3555,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? address, bool acceptsPointPayment, bool earnsPoints, bool earnsCoins)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? address, bool earnsPoints, bool earnsCoins)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _Outlet() when $default != null: -return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_that.earnsPoints,_that.earnsCoins);case _: +return $default(_that.id,_that.name,_that.address,_that.earnsPoints,_that.earnsCoins);case _: return orElse(); } @@ -3848,10 +3576,10 @@ return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_tha /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String name, String? address, bool acceptsPointPayment, bool earnsPoints, bool earnsCoins) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String name, String? address, bool earnsPoints, bool earnsCoins) $default,) {final _that = this; switch (_that) { case _Outlet(): -return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_that.earnsPoints,_that.earnsCoins);case _: +return $default(_that.id,_that.name,_that.address,_that.earnsPoints,_that.earnsCoins);case _: throw StateError('Unexpected subclass'); } @@ -3868,10 +3596,10 @@ return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_tha /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? address, bool acceptsPointPayment, bool earnsPoints, bool earnsCoins)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? address, bool earnsPoints, bool earnsCoins)? $default,) {final _that = this; switch (_that) { case _Outlet() when $default != null: -return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_that.earnsPoints,_that.earnsCoins);case _: +return $default(_that.id,_that.name,_that.address,_that.earnsPoints,_that.earnsCoins);case _: return null; } @@ -3883,14 +3611,12 @@ return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_tha class _Outlet implements Outlet { - const _Outlet({required this.id, required this.name, this.address, required this.acceptsPointPayment, required this.earnsPoints, required this.earnsCoins}); + const _Outlet({required this.id, required this.name, this.address, required this.earnsPoints, required this.earnsCoins}); @override final String id; @override final String name; @override final String? address; -/// Kasir di outlet ini menerima pembayaran EnakPoint. -@override final bool acceptsPointPayment; /// Belanja di outlet ini memberi EnakPoint. @override final bool earnsPoints; /// Belanja di outlet ini memberi EnakCoin. @@ -3906,18 +3632,18 @@ _$OutletCopyWith<_Outlet> get copyWith => __$OutletCopyWithImpl<_Outlet>(this, _ @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Outlet&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.address, address) || other.address == address)&&(identical(other.acceptsPointPayment, acceptsPointPayment) || other.acceptsPointPayment == acceptsPointPayment)&&(identical(other.earnsPoints, earnsPoints) || other.earnsPoints == earnsPoints)&&(identical(other.earnsCoins, earnsCoins) || other.earnsCoins == earnsCoins)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Outlet&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.address, address) || other.address == address)&&(identical(other.earnsPoints, earnsPoints) || other.earnsPoints == earnsPoints)&&(identical(other.earnsCoins, earnsCoins) || other.earnsCoins == earnsCoins)); } @override int get hashCode { - return Object.hash(runtimeType,id,name,address,acceptsPointPayment,earnsPoints,earnsCoins); + return Object.hash(runtimeType,id,name,address,earnsPoints,earnsCoins); } @override String toString() { - return 'Outlet(id: $id, name: $name, address: $address, acceptsPointPayment: $acceptsPointPayment, earnsPoints: $earnsPoints, earnsCoins: $earnsCoins)'; + return 'Outlet(id: $id, name: $name, address: $address, earnsPoints: $earnsPoints, earnsCoins: $earnsCoins)'; } @@ -3928,7 +3654,7 @@ abstract mixin class _$OutletCopyWith<$Res> implements $OutletCopyWith<$Res> { factory _$OutletCopyWith(_Outlet value, $Res Function(_Outlet) _then) = __$OutletCopyWithImpl; @override @useResult $Res call({ - String id, String name, String? address, bool acceptsPointPayment, bool earnsPoints, bool earnsCoins + String id, String name, String? address, bool earnsPoints, bool earnsCoins }); @@ -3945,13 +3671,12 @@ class __$OutletCopyWithImpl<$Res> /// Create a copy of Outlet /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? address = freezed,Object? acceptsPointPayment = null,Object? earnsPoints = null,Object? earnsCoins = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? address = freezed,Object? earnsPoints = null,Object? earnsCoins = null,}) { return _then(_Outlet( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable -as String?,acceptsPointPayment: null == acceptsPointPayment ? _self.acceptsPointPayment : acceptsPointPayment // ignore: cast_nullable_to_non_nullable -as bool,earnsPoints: null == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable +as String?,earnsPoints: null == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable as bool,earnsCoins: null == earnsCoins ? _self.earnsCoins : earnsCoins // ignore: cast_nullable_to_non_nullable as bool, )); @@ -5144,8 +4869,7 @@ as int, /// @nodoc mixin _$CustomerOrderPayment { - String get id; String get methodName; String get methodType; int get amount; int get refundAmount;/// Hanya pada pembayaran EnakPoint. - int? get pointsUsed; + String get id; String get methodName; String get methodType; int get amount; int get refundAmount; /// Create a copy of CustomerOrderPayment /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -5157,20 +4881,20 @@ $CustomerOrderPaymentCopyWith get copyWith => _$CustomerOr @override bool operator ==(Object other) { final _this = this as CustomerOrderPayment; - return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomerOrderPayment&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.methodName, _this.methodName) || other.methodName == _this.methodName)&&(identical(other.methodType, _this.methodType) || other.methodType == _this.methodType)&&(identical(other.amount, _this.amount) || other.amount == _this.amount)&&(identical(other.refundAmount, _this.refundAmount) || other.refundAmount == _this.refundAmount)&&(identical(other.pointsUsed, _this.pointsUsed) || other.pointsUsed == _this.pointsUsed)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomerOrderPayment&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.methodName, _this.methodName) || other.methodName == _this.methodName)&&(identical(other.methodType, _this.methodType) || other.methodType == _this.methodType)&&(identical(other.amount, _this.amount) || other.amount == _this.amount)&&(identical(other.refundAmount, _this.refundAmount) || other.refundAmount == _this.refundAmount)); } @override int get hashCode { final _this = this as CustomerOrderPayment; - return Object.hash(runtimeType,_this.id,_this.methodName,_this.methodType,_this.amount,_this.refundAmount,_this.pointsUsed); + return Object.hash(runtimeType,_this.id,_this.methodName,_this.methodType,_this.amount,_this.refundAmount); } @override String toString() { final _this = this as CustomerOrderPayment; - return 'CustomerOrderPayment(id: ${_this.id}, methodName: ${_this.methodName}, methodType: ${_this.methodType}, amount: ${_this.amount}, refundAmount: ${_this.refundAmount}, pointsUsed: ${_this.pointsUsed})'; + return 'CustomerOrderPayment(id: ${_this.id}, methodName: ${_this.methodName}, methodType: ${_this.methodType}, amount: ${_this.amount}, refundAmount: ${_this.refundAmount})'; } @@ -5181,7 +4905,7 @@ abstract mixin class $CustomerOrderPaymentCopyWith<$Res> { factory $CustomerOrderPaymentCopyWith(CustomerOrderPayment value, $Res Function(CustomerOrderPayment) _then) = _$CustomerOrderPaymentCopyWithImpl; @useResult $Res call({ - String id, String methodName, String methodType, int amount, int refundAmount, int? pointsUsed + String id, String methodName, String methodType, int amount, int refundAmount }); @@ -5198,15 +4922,14 @@ class _$CustomerOrderPaymentCopyWithImpl<$Res> /// Create a copy of CustomerOrderPayment /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? methodName = null,Object? methodType = null,Object? amount = null,Object? refundAmount = null,Object? pointsUsed = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? methodName = null,Object? methodType = null,Object? amount = null,Object? refundAmount = null,}) { return _then(CustomerOrderPayment( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,methodName: null == methodName ? _self.methodName : methodName // ignore: cast_nullable_to_non_nullable as String,methodType: null == methodType ? _self.methodType : methodType // ignore: cast_nullable_to_non_nullable as String,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable as int,refundAmount: null == refundAmount ? _self.refundAmount : refundAmount // ignore: cast_nullable_to_non_nullable -as int,pointsUsed: freezed == pointsUsed ? _self.pointsUsed : pointsUsed // ignore: cast_nullable_to_non_nullable -as int?, +as int, )); } @@ -5291,10 +5014,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String methodName, String methodType, int amount, int refundAmount, int? pointsUsed)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String methodName, String methodType, int amount, int refundAmount)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _CustomerOrderPayment() when $default != null: -return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount,_that.pointsUsed);case _: +return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount);case _: return orElse(); } @@ -5312,10 +5035,10 @@ return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.re /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String methodName, String methodType, int amount, int refundAmount, int? pointsUsed) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String methodName, String methodType, int amount, int refundAmount) $default,) {final _that = this; switch (_that) { case _CustomerOrderPayment(): -return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount,_that.pointsUsed);case _: +return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount);case _: throw StateError('Unexpected subclass'); } @@ -5332,10 +5055,10 @@ return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.re /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String methodName, String methodType, int amount, int refundAmount, int? pointsUsed)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String methodName, String methodType, int amount, int refundAmount)? $default,) {final _that = this; switch (_that) { case _CustomerOrderPayment() when $default != null: -return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount,_that.pointsUsed);case _: +return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount);case _: return null; } @@ -5346,8 +5069,8 @@ return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.re /// @nodoc -class _CustomerOrderPayment extends CustomerOrderPayment { - const _CustomerOrderPayment({required this.id, required this.methodName, required this.methodType, required this.amount, required this.refundAmount, this.pointsUsed}): super._(); +class _CustomerOrderPayment implements CustomerOrderPayment { + const _CustomerOrderPayment({required this.id, required this.methodName, required this.methodType, required this.amount, required this.refundAmount}); @override final String id; @@ -5355,8 +5078,6 @@ class _CustomerOrderPayment extends CustomerOrderPayment { @override final String methodType; @override final int amount; @override final int refundAmount; -/// Hanya pada pembayaran EnakPoint. -@override final int? pointsUsed; /// Create a copy of CustomerOrderPayment /// with the given fields replaced by the non-null parameter values. @@ -5368,18 +5089,18 @@ _$CustomerOrderPaymentCopyWith<_CustomerOrderPayment> get copyWith => __$Custome @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomerOrderPayment&&(identical(other.id, id) || other.id == id)&&(identical(other.methodName, methodName) || other.methodName == methodName)&&(identical(other.methodType, methodType) || other.methodType == methodType)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.refundAmount, refundAmount) || other.refundAmount == refundAmount)&&(identical(other.pointsUsed, pointsUsed) || other.pointsUsed == pointsUsed)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomerOrderPayment&&(identical(other.id, id) || other.id == id)&&(identical(other.methodName, methodName) || other.methodName == methodName)&&(identical(other.methodType, methodType) || other.methodType == methodType)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.refundAmount, refundAmount) || other.refundAmount == refundAmount)); } @override int get hashCode { - return Object.hash(runtimeType,id,methodName,methodType,amount,refundAmount,pointsUsed); + return Object.hash(runtimeType,id,methodName,methodType,amount,refundAmount); } @override String toString() { - return 'CustomerOrderPayment(id: $id, methodName: $methodName, methodType: $methodType, amount: $amount, refundAmount: $refundAmount, pointsUsed: $pointsUsed)'; + return 'CustomerOrderPayment(id: $id, methodName: $methodName, methodType: $methodType, amount: $amount, refundAmount: $refundAmount)'; } @@ -5390,7 +5111,7 @@ abstract mixin class _$CustomerOrderPaymentCopyWith<$Res> implements $CustomerOr factory _$CustomerOrderPaymentCopyWith(_CustomerOrderPayment value, $Res Function(_CustomerOrderPayment) _then) = __$CustomerOrderPaymentCopyWithImpl; @override @useResult $Res call({ - String id, String methodName, String methodType, int amount, int refundAmount, int? pointsUsed + String id, String methodName, String methodType, int amount, int refundAmount }); @@ -5407,14 +5128,312 @@ class __$CustomerOrderPaymentCopyWithImpl<$Res> /// Create a copy of CustomerOrderPayment /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? methodName = null,Object? methodType = null,Object? amount = null,Object? refundAmount = null,Object? pointsUsed = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? methodName = null,Object? methodType = null,Object? amount = null,Object? refundAmount = null,}) { return _then(_CustomerOrderPayment( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,methodName: null == methodName ? _self.methodName : methodName // ignore: cast_nullable_to_non_nullable as String,methodType: null == methodType ? _self.methodType : methodType // ignore: cast_nullable_to_non_nullable as String,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable as int,refundAmount: null == refundAmount ? _self.refundAmount : refundAmount // ignore: cast_nullable_to_non_nullable -as int,pointsUsed: freezed == pointsUsed ? _self.pointsUsed : pointsUsed // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$Voucher { + + String get id; String get name; String? get description; String? get imageUrl; VoucherType get type;/// Nilai voucher dalam rupiah. + int get faceValue;/// EnakPoint yang dipotong saat ditukar. + int get pointCost;/// Batas tukar per customer; `null` berarti tanpa batas. + int? get maxPerCustomer; DateTime? get validUntil;/// Sisa stok; `null` berarti stok tidak dihitung. + int? get available; +/// Create a copy of Voucher +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherCopyWith get copyWith => _$VoucherCopyWithImpl(this as Voucher, _$identity); + + + +@override +bool operator ==(Object other) { + final _this = this as Voucher; + return identical(this, other) || (other.runtimeType == runtimeType&&other is Voucher&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.description, _this.description) || other.description == _this.description)&&(identical(other.imageUrl, _this.imageUrl) || other.imageUrl == _this.imageUrl)&&(identical(other.type, _this.type) || other.type == _this.type)&&(identical(other.faceValue, _this.faceValue) || other.faceValue == _this.faceValue)&&(identical(other.pointCost, _this.pointCost) || other.pointCost == _this.pointCost)&&(identical(other.maxPerCustomer, _this.maxPerCustomer) || other.maxPerCustomer == _this.maxPerCustomer)&&(identical(other.validUntil, _this.validUntil) || other.validUntil == _this.validUntil)&&(identical(other.available, _this.available) || other.available == _this.available)); +} + + +@override +int get hashCode { + final _this = this as Voucher; + return Object.hash(runtimeType,_this.id,_this.name,_this.description,_this.imageUrl,_this.type,_this.faceValue,_this.pointCost,_this.maxPerCustomer,_this.validUntil,_this.available); +} + +@override +String toString() { + final _this = this as Voucher; + return 'Voucher(id: ${_this.id}, name: ${_this.name}, description: ${_this.description}, imageUrl: ${_this.imageUrl}, type: ${_this.type}, faceValue: ${_this.faceValue}, pointCost: ${_this.pointCost}, maxPerCustomer: ${_this.maxPerCustomer}, validUntil: ${_this.validUntil}, available: ${_this.available})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherCopyWith<$Res> { + factory $VoucherCopyWith(Voucher value, $Res Function(Voucher) _then) = _$VoucherCopyWithImpl; +@useResult +$Res call({ + String id, String name, String? description, String? imageUrl, VoucherType type, int faceValue, int pointCost, int? maxPerCustomer, DateTime? validUntil, int? available +}); + + + + +} +/// @nodoc +class _$VoucherCopyWithImpl<$Res> + implements $VoucherCopyWith<$Res> { + _$VoucherCopyWithImpl(this._self, this._then); + + final Voucher _self; + final $Res Function(Voucher) _then; + +/// Create a copy of Voucher +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? imageUrl = freezed,Object? type = null,Object? faceValue = null,Object? pointCost = null,Object? maxPerCustomer = freezed,Object? validUntil = freezed,Object? available = freezed,}) { + return _then(Voucher( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable +as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as VoucherType,faceValue: null == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int,pointCost: null == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int,maxPerCustomer: freezed == maxPerCustomer ? _self.maxPerCustomer : maxPerCustomer // ignore: cast_nullable_to_non_nullable +as int?,validUntil: freezed == validUntil ? _self.validUntil : validUntil // ignore: cast_nullable_to_non_nullable +as DateTime?,available: freezed == available ? _self.available : available // ignore: cast_nullable_to_non_nullable +as int?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Voucher]. +extension VoucherPatterns on Voucher { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Voucher value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Voucher() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Voucher value) $default,){ +final _that = this; +switch (_that) { +case _Voucher(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Voucher value)? $default,){ +final _that = this; +switch (_that) { +case _Voucher() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? description, String? imageUrl, VoucherType type, int faceValue, int pointCost, int? maxPerCustomer, DateTime? validUntil, int? available)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Voucher() when $default != null: +return $default(_that.id,_that.name,_that.description,_that.imageUrl,_that.type,_that.faceValue,_that.pointCost,_that.maxPerCustomer,_that.validUntil,_that.available);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String name, String? description, String? imageUrl, VoucherType type, int faceValue, int pointCost, int? maxPerCustomer, DateTime? validUntil, int? available) $default,) {final _that = this; +switch (_that) { +case _Voucher(): +return $default(_that.id,_that.name,_that.description,_that.imageUrl,_that.type,_that.faceValue,_that.pointCost,_that.maxPerCustomer,_that.validUntil,_that.available);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? description, String? imageUrl, VoucherType type, int faceValue, int pointCost, int? maxPerCustomer, DateTime? validUntil, int? available)? $default,) {final _that = this; +switch (_that) { +case _Voucher() when $default != null: +return $default(_that.id,_that.name,_that.description,_that.imageUrl,_that.type,_that.faceValue,_that.pointCost,_that.maxPerCustomer,_that.validUntil,_that.available);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _Voucher extends Voucher { + const _Voucher({required this.id, required this.name, this.description, this.imageUrl, required this.type, required this.faceValue, required this.pointCost, this.maxPerCustomer, this.validUntil, this.available}): super._(); + + +@override final String id; +@override final String name; +@override final String? description; +@override final String? imageUrl; +@override final VoucherType type; +/// Nilai voucher dalam rupiah. +@override final int faceValue; +/// EnakPoint yang dipotong saat ditukar. +@override final int pointCost; +/// Batas tukar per customer; `null` berarti tanpa batas. +@override final int? maxPerCustomer; +@override final DateTime? validUntil; +/// Sisa stok; `null` berarti stok tidak dihitung. +@override final int? available; + +/// Create a copy of Voucher +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherCopyWith<_Voucher> get copyWith => __$VoucherCopyWithImpl<_Voucher>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Voucher&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.type, type) || other.type == type)&&(identical(other.faceValue, faceValue) || other.faceValue == faceValue)&&(identical(other.pointCost, pointCost) || other.pointCost == pointCost)&&(identical(other.maxPerCustomer, maxPerCustomer) || other.maxPerCustomer == maxPerCustomer)&&(identical(other.validUntil, validUntil) || other.validUntil == validUntil)&&(identical(other.available, available) || other.available == available)); +} + + +@override +int get hashCode { + return Object.hash(runtimeType,id,name,description,imageUrl,type,faceValue,pointCost,maxPerCustomer,validUntil,available); +} + +@override +String toString() { + return 'Voucher(id: $id, name: $name, description: $description, imageUrl: $imageUrl, type: $type, faceValue: $faceValue, pointCost: $pointCost, maxPerCustomer: $maxPerCustomer, validUntil: $validUntil, available: $available)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherCopyWith<$Res> implements $VoucherCopyWith<$Res> { + factory _$VoucherCopyWith(_Voucher value, $Res Function(_Voucher) _then) = __$VoucherCopyWithImpl; +@override @useResult +$Res call({ + String id, String name, String? description, String? imageUrl, VoucherType type, int faceValue, int pointCost, int? maxPerCustomer, DateTime? validUntil, int? available +}); + + + + +} +/// @nodoc +class __$VoucherCopyWithImpl<$Res> + implements _$VoucherCopyWith<$Res> { + __$VoucherCopyWithImpl(this._self, this._then); + + final _Voucher _self; + final $Res Function(_Voucher) _then; + +/// Create a copy of Voucher +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? imageUrl = freezed,Object? type = null,Object? faceValue = null,Object? pointCost = null,Object? maxPerCustomer = freezed,Object? validUntil = freezed,Object? available = freezed,}) { + return _then(_Voucher( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable +as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as VoucherType,faceValue: null == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int,pointCost: null == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int,maxPerCustomer: freezed == maxPerCustomer ? _self.maxPerCustomer : maxPerCustomer // ignore: cast_nullable_to_non_nullable +as int?,validUntil: freezed == validUntil ? _self.validUntil : validUntil // ignore: cast_nullable_to_non_nullable +as DateTime?,available: freezed == available ? _self.available : available // ignore: cast_nullable_to_non_nullable as int?, )); } @@ -5422,6 +5441,873 @@ as int?, } +/// @nodoc +mixin _$VoucherRedemption { + + String get id; String get voucherId; String get voucherName; String? get voucherImageUrl; VoucherType get voucherType; VoucherRedemptionStatus get status; int get faceValue; int get pointCost;/// `null` bila voucher tidak memakai kode; customer menunjukkan layar +/// voucher ke kasir. + String? get code; DateTime? get codeExpiresAt; DateTime? get completedAt; DateTime? get createdAt; +/// Create a copy of VoucherRedemption +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherRedemptionCopyWith get copyWith => _$VoucherRedemptionCopyWithImpl(this as VoucherRedemption, _$identity); + + + +@override +bool operator ==(Object other) { + final _this = this as VoucherRedemption; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedemption&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.voucherId, _this.voucherId) || other.voucherId == _this.voucherId)&&(identical(other.voucherName, _this.voucherName) || other.voucherName == _this.voucherName)&&(identical(other.voucherImageUrl, _this.voucherImageUrl) || other.voucherImageUrl == _this.voucherImageUrl)&&(identical(other.voucherType, _this.voucherType) || other.voucherType == _this.voucherType)&&(identical(other.status, _this.status) || other.status == _this.status)&&(identical(other.faceValue, _this.faceValue) || other.faceValue == _this.faceValue)&&(identical(other.pointCost, _this.pointCost) || other.pointCost == _this.pointCost)&&(identical(other.code, _this.code) || other.code == _this.code)&&(identical(other.codeExpiresAt, _this.codeExpiresAt) || other.codeExpiresAt == _this.codeExpiresAt)&&(identical(other.completedAt, _this.completedAt) || other.completedAt == _this.completedAt)&&(identical(other.createdAt, _this.createdAt) || other.createdAt == _this.createdAt)); +} + + +@override +int get hashCode { + final _this = this as VoucherRedemption; + return Object.hash(runtimeType,_this.id,_this.voucherId,_this.voucherName,_this.voucherImageUrl,_this.voucherType,_this.status,_this.faceValue,_this.pointCost,_this.code,_this.codeExpiresAt,_this.completedAt,_this.createdAt); +} + +@override +String toString() { + final _this = this as VoucherRedemption; + return 'VoucherRedemption(id: ${_this.id}, voucherId: ${_this.voucherId}, voucherName: ${_this.voucherName}, voucherImageUrl: ${_this.voucherImageUrl}, voucherType: ${_this.voucherType}, status: ${_this.status}, faceValue: ${_this.faceValue}, pointCost: ${_this.pointCost}, code: ${_this.code}, codeExpiresAt: ${_this.codeExpiresAt}, completedAt: ${_this.completedAt}, createdAt: ${_this.createdAt})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherRedemptionCopyWith<$Res> { + factory $VoucherRedemptionCopyWith(VoucherRedemption value, $Res Function(VoucherRedemption) _then) = _$VoucherRedemptionCopyWithImpl; +@useResult +$Res call({ + String id, String voucherId, String voucherName, String? voucherImageUrl, VoucherType voucherType, VoucherRedemptionStatus status, int faceValue, int pointCost, String? code, DateTime? codeExpiresAt, DateTime? completedAt, DateTime? createdAt +}); + + + + +} +/// @nodoc +class _$VoucherRedemptionCopyWithImpl<$Res> + implements $VoucherRedemptionCopyWith<$Res> { + _$VoucherRedemptionCopyWithImpl(this._self, this._then); + + final VoucherRedemption _self; + final $Res Function(VoucherRedemption) _then; + +/// Create a copy of VoucherRedemption +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? voucherId = null,Object? voucherName = null,Object? voucherImageUrl = freezed,Object? voucherType = null,Object? status = null,Object? faceValue = null,Object? pointCost = null,Object? code = freezed,Object? codeExpiresAt = freezed,Object? completedAt = freezed,Object? createdAt = freezed,}) { + return _then(VoucherRedemption( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,voucherId: null == voucherId ? _self.voucherId : voucherId // ignore: cast_nullable_to_non_nullable +as String,voucherName: null == voucherName ? _self.voucherName : voucherName // ignore: cast_nullable_to_non_nullable +as String,voucherImageUrl: freezed == voucherImageUrl ? _self.voucherImageUrl : voucherImageUrl // ignore: cast_nullable_to_non_nullable +as String?,voucherType: null == voucherType ? _self.voucherType : voucherType // ignore: cast_nullable_to_non_nullable +as VoucherType,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as VoucherRedemptionStatus,faceValue: null == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int,pointCost: null == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable +as String?,codeExpiresAt: freezed == codeExpiresAt ? _self.codeExpiresAt : codeExpiresAt // ignore: cast_nullable_to_non_nullable +as DateTime?,completedAt: freezed == completedAt ? _self.completedAt : completedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [VoucherRedemption]. +extension VoucherRedemptionPatterns on VoucherRedemption { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherRedemption value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _VoucherRedemption() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _VoucherRedemption value) $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemption(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherRedemption value)? $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemption() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String voucherId, String voucherName, String? voucherImageUrl, VoucherType voucherType, VoucherRedemptionStatus status, int faceValue, int pointCost, String? code, DateTime? codeExpiresAt, DateTime? completedAt, DateTime? createdAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _VoucherRedemption() when $default != null: +return $default(_that.id,_that.voucherId,_that.voucherName,_that.voucherImageUrl,_that.voucherType,_that.status,_that.faceValue,_that.pointCost,_that.code,_that.codeExpiresAt,_that.completedAt,_that.createdAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String voucherId, String voucherName, String? voucherImageUrl, VoucherType voucherType, VoucherRedemptionStatus status, int faceValue, int pointCost, String? code, DateTime? codeExpiresAt, DateTime? completedAt, DateTime? createdAt) $default,) {final _that = this; +switch (_that) { +case _VoucherRedemption(): +return $default(_that.id,_that.voucherId,_that.voucherName,_that.voucherImageUrl,_that.voucherType,_that.status,_that.faceValue,_that.pointCost,_that.code,_that.codeExpiresAt,_that.completedAt,_that.createdAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String voucherId, String voucherName, String? voucherImageUrl, VoucherType voucherType, VoucherRedemptionStatus status, int faceValue, int pointCost, String? code, DateTime? codeExpiresAt, DateTime? completedAt, DateTime? createdAt)? $default,) {final _that = this; +switch (_that) { +case _VoucherRedemption() when $default != null: +return $default(_that.id,_that.voucherId,_that.voucherName,_that.voucherImageUrl,_that.voucherType,_that.status,_that.faceValue,_that.pointCost,_that.code,_that.codeExpiresAt,_that.completedAt,_that.createdAt);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _VoucherRedemption implements VoucherRedemption { + const _VoucherRedemption({required this.id, required this.voucherId, required this.voucherName, this.voucherImageUrl, required this.voucherType, required this.status, required this.faceValue, required this.pointCost, this.code, this.codeExpiresAt, this.completedAt, this.createdAt}); + + +@override final String id; +@override final String voucherId; +@override final String voucherName; +@override final String? voucherImageUrl; +@override final VoucherType voucherType; +@override final VoucherRedemptionStatus status; +@override final int faceValue; +@override final int pointCost; +/// `null` bila voucher tidak memakai kode; customer menunjukkan layar +/// voucher ke kasir. +@override final String? code; +@override final DateTime? codeExpiresAt; +@override final DateTime? completedAt; +@override final DateTime? createdAt; + +/// Create a copy of VoucherRedemption +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherRedemptionCopyWith<_VoucherRedemption> get copyWith => __$VoucherRedemptionCopyWithImpl<_VoucherRedemption>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherRedemption&&(identical(other.id, id) || other.id == id)&&(identical(other.voucherId, voucherId) || other.voucherId == voucherId)&&(identical(other.voucherName, voucherName) || other.voucherName == voucherName)&&(identical(other.voucherImageUrl, voucherImageUrl) || other.voucherImageUrl == voucherImageUrl)&&(identical(other.voucherType, voucherType) || other.voucherType == voucherType)&&(identical(other.status, status) || other.status == status)&&(identical(other.faceValue, faceValue) || other.faceValue == faceValue)&&(identical(other.pointCost, pointCost) || other.pointCost == pointCost)&&(identical(other.code, code) || other.code == code)&&(identical(other.codeExpiresAt, codeExpiresAt) || other.codeExpiresAt == codeExpiresAt)&&(identical(other.completedAt, completedAt) || other.completedAt == completedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); +} + + +@override +int get hashCode { + return Object.hash(runtimeType,id,voucherId,voucherName,voucherImageUrl,voucherType,status,faceValue,pointCost,code,codeExpiresAt,completedAt,createdAt); +} + +@override +String toString() { + return 'VoucherRedemption(id: $id, voucherId: $voucherId, voucherName: $voucherName, voucherImageUrl: $voucherImageUrl, voucherType: $voucherType, status: $status, faceValue: $faceValue, pointCost: $pointCost, code: $code, codeExpiresAt: $codeExpiresAt, completedAt: $completedAt, createdAt: $createdAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherRedemptionCopyWith<$Res> implements $VoucherRedemptionCopyWith<$Res> { + factory _$VoucherRedemptionCopyWith(_VoucherRedemption value, $Res Function(_VoucherRedemption) _then) = __$VoucherRedemptionCopyWithImpl; +@override @useResult +$Res call({ + String id, String voucherId, String voucherName, String? voucherImageUrl, VoucherType voucherType, VoucherRedemptionStatus status, int faceValue, int pointCost, String? code, DateTime? codeExpiresAt, DateTime? completedAt, DateTime? createdAt +}); + + + + +} +/// @nodoc +class __$VoucherRedemptionCopyWithImpl<$Res> + implements _$VoucherRedemptionCopyWith<$Res> { + __$VoucherRedemptionCopyWithImpl(this._self, this._then); + + final _VoucherRedemption _self; + final $Res Function(_VoucherRedemption) _then; + +/// Create a copy of VoucherRedemption +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? voucherId = null,Object? voucherName = null,Object? voucherImageUrl = freezed,Object? voucherType = null,Object? status = null,Object? faceValue = null,Object? pointCost = null,Object? code = freezed,Object? codeExpiresAt = freezed,Object? completedAt = freezed,Object? createdAt = freezed,}) { + return _then(_VoucherRedemption( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,voucherId: null == voucherId ? _self.voucherId : voucherId // ignore: cast_nullable_to_non_nullable +as String,voucherName: null == voucherName ? _self.voucherName : voucherName // ignore: cast_nullable_to_non_nullable +as String,voucherImageUrl: freezed == voucherImageUrl ? _self.voucherImageUrl : voucherImageUrl // ignore: cast_nullable_to_non_nullable +as String?,voucherType: null == voucherType ? _self.voucherType : voucherType // ignore: cast_nullable_to_non_nullable +as VoucherType,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as VoucherRedemptionStatus,faceValue: null == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int,pointCost: null == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable +as String?,codeExpiresAt: freezed == codeExpiresAt ? _self.codeExpiresAt : codeExpiresAt // ignore: cast_nullable_to_non_nullable +as DateTime?,completedAt: freezed == completedAt ? _self.completedAt : completedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + + +} + +/// @nodoc +mixin _$VoucherRedemptionPage { + + List get items; int get page; int get totalPages; +/// Create a copy of VoucherRedemptionPage +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherRedemptionPageCopyWith get copyWith => _$VoucherRedemptionPageCopyWithImpl(this as VoucherRedemptionPage, _$identity); + + + +@override +bool operator ==(Object other) { + final _this = this as VoucherRedemptionPage; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedemptionPage&&const DeepCollectionEquality().equals(other.items, _this.items)&&(identical(other.page, _this.page) || other.page == _this.page)&&(identical(other.totalPages, _this.totalPages) || other.totalPages == _this.totalPages)); +} + + +@override +int get hashCode { + final _this = this as VoucherRedemptionPage; + return Object.hash(runtimeType,const DeepCollectionEquality().hash(_this.items),_this.page,_this.totalPages); +} + +@override +String toString() { + final _this = this as VoucherRedemptionPage; + return 'VoucherRedemptionPage(items: ${_this.items}, page: ${_this.page}, totalPages: ${_this.totalPages})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherRedemptionPageCopyWith<$Res> { + factory $VoucherRedemptionPageCopyWith(VoucherRedemptionPage value, $Res Function(VoucherRedemptionPage) _then) = _$VoucherRedemptionPageCopyWithImpl; +@useResult +$Res call({ + List items, int page, int totalPages +}); + + + + +} +/// @nodoc +class _$VoucherRedemptionPageCopyWithImpl<$Res> + implements $VoucherRedemptionPageCopyWith<$Res> { + _$VoucherRedemptionPageCopyWithImpl(this._self, this._then); + + final VoucherRedemptionPage _self; + final $Res Function(VoucherRedemptionPage) _then; + +/// Create a copy of VoucherRedemptionPage +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? items = null,Object? page = null,Object? totalPages = null,}) { + return _then(VoucherRedemptionPage( +items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable +as List,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable +as int,totalPages: null == totalPages ? _self.totalPages : totalPages // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [VoucherRedemptionPage]. +extension VoucherRedemptionPagePatterns on VoucherRedemptionPage { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherRedemptionPage value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _VoucherRedemptionPage() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _VoucherRedemptionPage value) $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemptionPage(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherRedemptionPage value)? $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemptionPage() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List items, int page, int totalPages)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _VoucherRedemptionPage() when $default != null: +return $default(_that.items,_that.page,_that.totalPages);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List items, int page, int totalPages) $default,) {final _that = this; +switch (_that) { +case _VoucherRedemptionPage(): +return $default(_that.items,_that.page,_that.totalPages);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List items, int page, int totalPages)? $default,) {final _that = this; +switch (_that) { +case _VoucherRedemptionPage() when $default != null: +return $default(_that.items,_that.page,_that.totalPages);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _VoucherRedemptionPage implements VoucherRedemptionPage { + const _VoucherRedemptionPage({required List items, required this.page, required this.totalPages}): _items = items; + + + final List _items; +@override List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); +} + +@override final int page; +@override final int totalPages; + +/// Create a copy of VoucherRedemptionPage +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherRedemptionPageCopyWith<_VoucherRedemptionPage> get copyWith => __$VoucherRedemptionPageCopyWithImpl<_VoucherRedemptionPage>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherRedemptionPage&&const DeepCollectionEquality().equals(other.items, _items)&&(identical(other.page, page) || other.page == page)&&(identical(other.totalPages, totalPages) || other.totalPages == totalPages)); +} + + +@override +int get hashCode { + return Object.hash(runtimeType,const DeepCollectionEquality().hash(_items),page,totalPages); +} + +@override +String toString() { + return 'VoucherRedemptionPage(items: $items, page: $page, totalPages: $totalPages)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherRedemptionPageCopyWith<$Res> implements $VoucherRedemptionPageCopyWith<$Res> { + factory _$VoucherRedemptionPageCopyWith(_VoucherRedemptionPage value, $Res Function(_VoucherRedemptionPage) _then) = __$VoucherRedemptionPageCopyWithImpl; +@override @useResult +$Res call({ + List items, int page, int totalPages +}); + + + + +} +/// @nodoc +class __$VoucherRedemptionPageCopyWithImpl<$Res> + implements _$VoucherRedemptionPageCopyWith<$Res> { + __$VoucherRedemptionPageCopyWithImpl(this._self, this._then); + + final _VoucherRedemptionPage _self; + final $Res Function(_VoucherRedemptionPage) _then; + +/// Create a copy of VoucherRedemptionPage +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? items = null,Object? page = null,Object? totalPages = null,}) { + return _then(_VoucherRedemptionPage( +items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable +as List,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable +as int,totalPages: null == totalPages ? _self.totalPages : totalPages // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$VoucherRedeemResult { + + VoucherRedemption get redemption;/// Saldo EnakPoint setelah ditukar. + int get pointBalance; bool get replayed; +/// Create a copy of VoucherRedeemResult +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherRedeemResultCopyWith get copyWith => _$VoucherRedeemResultCopyWithImpl(this as VoucherRedeemResult, _$identity); + + + +@override +bool operator ==(Object other) { + final _this = this as VoucherRedeemResult; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedeemResult&&(identical(other.redemption, _this.redemption) || other.redemption == _this.redemption)&&(identical(other.pointBalance, _this.pointBalance) || other.pointBalance == _this.pointBalance)&&(identical(other.replayed, _this.replayed) || other.replayed == _this.replayed)); +} + + +@override +int get hashCode { + final _this = this as VoucherRedeemResult; + return Object.hash(runtimeType,_this.redemption,_this.pointBalance,_this.replayed); +} + +@override +String toString() { + final _this = this as VoucherRedeemResult; + return 'VoucherRedeemResult(redemption: ${_this.redemption}, pointBalance: ${_this.pointBalance}, replayed: ${_this.replayed})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherRedeemResultCopyWith<$Res> { + factory $VoucherRedeemResultCopyWith(VoucherRedeemResult value, $Res Function(VoucherRedeemResult) _then) = _$VoucherRedeemResultCopyWithImpl; +@useResult +$Res call({ + VoucherRedemption redemption, int pointBalance, bool replayed +}); + + +$VoucherRedemptionCopyWith<$Res> get redemption; + +} +/// @nodoc +class _$VoucherRedeemResultCopyWithImpl<$Res> + implements $VoucherRedeemResultCopyWith<$Res> { + _$VoucherRedeemResultCopyWithImpl(this._self, this._then); + + final VoucherRedeemResult _self; + final $Res Function(VoucherRedeemResult) _then; + +/// Create a copy of VoucherRedeemResult +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? redemption = null,Object? pointBalance = null,Object? replayed = null,}) { + return _then(VoucherRedeemResult( +redemption: null == redemption ? _self.redemption : redemption // ignore: cast_nullable_to_non_nullable +as VoucherRedemption,pointBalance: null == pointBalance ? _self.pointBalance : pointBalance // ignore: cast_nullable_to_non_nullable +as int,replayed: null == replayed ? _self.replayed : replayed // ignore: cast_nullable_to_non_nullable +as bool, + )); +} +/// Create a copy of VoucherRedeemResult +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$VoucherRedemptionCopyWith<$Res> get redemption { + + return $VoucherRedemptionCopyWith<$Res>(_self.redemption, (value) { + return _then(_self.copyWith(redemption: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [VoucherRedeemResult]. +extension VoucherRedeemResultPatterns on VoucherRedeemResult { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherRedeemResult value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _VoucherRedeemResult() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _VoucherRedeemResult value) $default,){ +final _that = this; +switch (_that) { +case _VoucherRedeemResult(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherRedeemResult value)? $default,){ +final _that = this; +switch (_that) { +case _VoucherRedeemResult() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( VoucherRedemption redemption, int pointBalance, bool replayed)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _VoucherRedeemResult() when $default != null: +return $default(_that.redemption,_that.pointBalance,_that.replayed);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( VoucherRedemption redemption, int pointBalance, bool replayed) $default,) {final _that = this; +switch (_that) { +case _VoucherRedeemResult(): +return $default(_that.redemption,_that.pointBalance,_that.replayed);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( VoucherRedemption redemption, int pointBalance, bool replayed)? $default,) {final _that = this; +switch (_that) { +case _VoucherRedeemResult() when $default != null: +return $default(_that.redemption,_that.pointBalance,_that.replayed);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _VoucherRedeemResult implements VoucherRedeemResult { + const _VoucherRedeemResult({required this.redemption, required this.pointBalance, required this.replayed}); + + +@override final VoucherRedemption redemption; +/// Saldo EnakPoint setelah ditukar. +@override final int pointBalance; +@override final bool replayed; + +/// Create a copy of VoucherRedeemResult +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherRedeemResultCopyWith<_VoucherRedeemResult> get copyWith => __$VoucherRedeemResultCopyWithImpl<_VoucherRedeemResult>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherRedeemResult&&(identical(other.redemption, redemption) || other.redemption == redemption)&&(identical(other.pointBalance, pointBalance) || other.pointBalance == pointBalance)&&(identical(other.replayed, replayed) || other.replayed == replayed)); +} + + +@override +int get hashCode { + return Object.hash(runtimeType,redemption,pointBalance,replayed); +} + +@override +String toString() { + return 'VoucherRedeemResult(redemption: $redemption, pointBalance: $pointBalance, replayed: $replayed)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherRedeemResultCopyWith<$Res> implements $VoucherRedeemResultCopyWith<$Res> { + factory _$VoucherRedeemResultCopyWith(_VoucherRedeemResult value, $Res Function(_VoucherRedeemResult) _then) = __$VoucherRedeemResultCopyWithImpl; +@override @useResult +$Res call({ + VoucherRedemption redemption, int pointBalance, bool replayed +}); + + +@override $VoucherRedemptionCopyWith<$Res> get redemption; + +} +/// @nodoc +class __$VoucherRedeemResultCopyWithImpl<$Res> + implements _$VoucherRedeemResultCopyWith<$Res> { + __$VoucherRedeemResultCopyWithImpl(this._self, this._then); + + final _VoucherRedeemResult _self; + final $Res Function(_VoucherRedeemResult) _then; + +/// Create a copy of VoucherRedeemResult +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? redemption = null,Object? pointBalance = null,Object? replayed = null,}) { + return _then(_VoucherRedeemResult( +redemption: null == redemption ? _self.redemption : redemption // ignore: cast_nullable_to_non_nullable +as VoucherRedemption,pointBalance: null == pointBalance ? _self.pointBalance : pointBalance // ignore: cast_nullable_to_non_nullable +as int,replayed: null == replayed ? _self.replayed : replayed // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +/// Create a copy of VoucherRedeemResult +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$VoucherRedemptionCopyWith<$Res> get redemption { + + return $VoucherRedemptionCopyWith<$Res>(_self.redemption, (value) { + return _then(_self.copyWith(redemption: value)); + }); +} +} + /// @nodoc mixin _$WalletFailure { diff --git a/lib/env.dart b/lib/env.dart index 813eb6f..be9267f 100644 --- a/lib/env.dart +++ b/lib/env.dart @@ -9,8 +9,8 @@ abstract class Env { @dev class DevEnv implements Env { @override - // String get baseUrl => 'http://192.168.1.5:4000'; // example value - String get baseUrl => 'https://enaklo-pos-api.altru.id'; // example value + String get baseUrl => 'http://194.233.78.1:4001'; // example value + // String get baseUrl => 'https://enaklo-pos-api.altru.id'; // example value } @Injectable(as: Env) diff --git a/lib/infrastructure/game/datasources/remote_data_provider.dart b/lib/infrastructure/game/datasources/remote_data_provider.dart deleted file mode 100644 index 2178867..0000000 --- a/lib/infrastructure/game/datasources/remote_data_provider.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:developer'; - -import 'package:data_channel/data_channel.dart'; -import 'package:injectable/injectable.dart'; - -import '../../../common/api/api_client.dart'; -import '../../../common/api/api_failure.dart'; -import '../../../common/function/app_function.dart'; -import '../../../common/url/api_path.dart'; -import '../../../domain/game/game.dart'; -import '../game_dtos.dart'; - -@injectable -class GameRemoteDataProvider { - final ApiClient _apiClient; - final String _logName = "GameRemoteDataProvider"; - - GameRemoteDataProvider(this._apiClient); - - Future> ferrisWheel() async { - try { - final response = await _apiClient.get( - ApiPath.ferrisWheel, - headers: getAuthorizationHeader(), - ); - - if (response.data['code'] == 401) { - return DC.error( - GameFailure.serverError(ApiFailure.unauthorized('Session Expired')), - ); - } - - if (response.data['status'] == false) { - return DC.error( - GameFailure.dynamicErrorMessage('Terjadi kesalahan coba lagi nanti'), - ); - } - - final dto = GameDto.fromJson(response.data['data']['data']['game']); - - return DC.data(dto); - } on ApiFailure catch (e, s) { - log('ferrisWheel', name: _logName, error: e, stackTrace: s); - return DC.error(GameFailure.serverError(e)); - } - } - - Future> spin({required String gameId}) async { - try { - final response = await _apiClient.post( - ApiPath.spin, - headers: getAuthorizationHeader(), - data: {'spin_id': gameId}, - ); - - if (response.data is! Map || response.data['success'] != true) { - final errors = response.data is Map ? response.data['errors'] : null; - final code = errors is List && errors.isNotEmpty && errors.first is Map - ? errors.first['code']?.toString() - : null; - - return DC.error( - GameFailure.dynamicErrorMessage( - code == '304' - ? 'EnakCoin kurang, game sedang tidak aktif, atau hadiah baru ' - 'saja habis. EnakCoin kamu tidak terpotong, silakan coba lagi.' - : 'Terjadi kesalahan, coba lagi', - ), - ); - } - - final dto = SpinResultDto.fromJson( - Map.from(response.data['data'] as Map), - ); - return DC.data(dto); - } on ApiFailure catch (e, s) { - log('spin', name: _logName, error: e, stackTrace: s); - return DC.error(GameFailure.serverError(e)); - } - } -} diff --git a/lib/infrastructure/game/dto/game_dto.dart b/lib/infrastructure/game/dto/game_dto.dart deleted file mode 100644 index 6fef223..0000000 --- a/lib/infrastructure/game/dto/game_dto.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of '../game_dtos.dart'; - -@freezed -abstract class GameDto with _$GameDto { - const factory GameDto({ - @JsonKey(name: 'id') String? id, - @JsonKey(name: 'name') String? name, - @JsonKey(name: 'type') String? type, - @JsonKey(name: 'is_active') bool? isActive, - @JsonKey(name: 'metadata') Map? metadata, - @JsonKey(name: 'prizes') List? prizes, - @JsonKey(name: 'created_at') String? createdAt, - @JsonKey(name: 'updated_at') String? updatedAt, - }) = _GameDto; - - factory GameDto.fromJson(Map json) => - _$GameDtoFromJson(json); - - const GameDto._(); - - Game toDomain() => Game( - id: id ?? '', - name: name ?? '', - type: type ?? '', - isActive: isActive ?? false, - metadata: metadata ?? {}, - prizes: prizes?.map((e) => e.toDomain()).toList() ?? [], - createdAt: createdAt ?? '', - updatedAt: updatedAt ?? '', - ); -} diff --git a/lib/infrastructure/game/dto/game_prize_dto.dart b/lib/infrastructure/game/dto/game_prize_dto.dart deleted file mode 100644 index c5ba716..0000000 --- a/lib/infrastructure/game/dto/game_prize_dto.dart +++ /dev/null @@ -1,27 +0,0 @@ -part of '../game_dtos.dart'; - -@freezed -abstract class GamePrizeDto with _$GamePrizeDto { - const factory GamePrizeDto({ - @JsonKey(name: 'id') String? id, - @JsonKey(name: 'game_id') String? gameId, - @JsonKey(name: 'name') String? name, - @JsonKey(name: 'metadata') Map? metadata, - @JsonKey(name: 'created_at') String? createdAt, - @JsonKey(name: 'updated_at') String? updatedAt, - }) = _GamePrizeDto; - - factory GamePrizeDto.fromJson(Map json) => - _$GamePrizeDtoFromJson(json); - - const GamePrizeDto._(); - - GamePrize toDomain() => GamePrize( - id: id ?? '', - gameId: gameId ?? '', - name: name ?? '', - metadata: metadata ?? {}, - createdAt: createdAt ?? '', - updatedAt: updatedAt ?? '', - ); -} diff --git a/lib/infrastructure/game/dto/spin_result_dto.dart b/lib/infrastructure/game/dto/spin_result_dto.dart deleted file mode 100644 index 73c72c5..0000000 --- a/lib/infrastructure/game/dto/spin_result_dto.dart +++ /dev/null @@ -1,47 +0,0 @@ -part of '../game_dtos.dart'; - -@freezed -abstract class SpinResultDto with _$SpinResultDto { - const factory SpinResultDto({ - @JsonKey(name: 'game_play') GamePlayDto? gamePlay, - @JsonKey(name: 'prize_won') PrizeWonDto? prizeWon, - @JsonKey(name: 'coins_remaining') int? coinsRemaining, - }) = _SpinResultDto; - - factory SpinResultDto.fromJson(Map json) => - _$SpinResultDtoFromJson(json); - - const SpinResultDto._(); - - SpinResult toDomain() => SpinResult( - gamePlayId: gamePlay?.id ?? '', - coinsUsed: gamePlay?.coinsUsed ?? 0, - prizeId: prizeWon?.id ?? '', - prizeName: prizeWon?.name ?? '', - coinsRemaining: coinsRemaining ?? 0, - ); -} - -@freezed -abstract class GamePlayDto with _$GamePlayDto { - const factory GamePlayDto({ - @JsonKey(name: 'id') String? id, - @JsonKey(name: 'game_id') String? gameId, - @JsonKey(name: 'coins_used') int? coinsUsed, - @JsonKey(name: 'created_at') String? createdAt, - }) = _GamePlayDto; - - factory GamePlayDto.fromJson(Map json) => - _$GamePlayDtoFromJson(json); -} - -@freezed -abstract class PrizeWonDto with _$PrizeWonDto { - const factory PrizeWonDto({ - @JsonKey(name: 'id') String? id, - @JsonKey(name: 'name') String? name, - }) = _PrizeWonDto; - - factory PrizeWonDto.fromJson(Map json) => - _$PrizeWonDtoFromJson(json); -} diff --git a/lib/infrastructure/game/game_dtos.dart b/lib/infrastructure/game/game_dtos.dart deleted file mode 100644 index 58e7404..0000000 --- a/lib/infrastructure/game/game_dtos.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -import '../../domain/game/game.dart'; - -part 'game_dtos.freezed.dart'; -part 'game_dtos.g.dart'; - -part 'dto/game_dto.dart'; -part 'dto/game_prize_dto.dart'; -part 'dto/spin_result_dto.dart'; diff --git a/lib/infrastructure/game/game_dtos.freezed.dart b/lib/infrastructure/game/game_dtos.freezed.dart deleted file mode 100644 index aeff44f..0000000 --- a/lib/infrastructure/game/game_dtos.freezed.dart +++ /dev/null @@ -1,1491 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'game_dtos.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// GENERATED CODE - DO NOT MODIFY BY HAND -// dart format off -T _$identity(T value) => value; - -/// @nodoc -mixin _$GameDto { - -@JsonKey(name: 'id') String? get id;@JsonKey(name: 'name') String? get name;@JsonKey(name: 'type') String? get type;@JsonKey(name: 'is_active') bool? get isActive;@JsonKey(name: 'metadata') Map? get metadata;@JsonKey(name: 'prizes') List? get prizes;@JsonKey(name: 'created_at') String? get createdAt;@JsonKey(name: 'updated_at') String? get updatedAt; -/// Create a copy of GameDto -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$GameDtoCopyWith get copyWith => _$GameDtoCopyWithImpl(this as GameDto, _$identity); - - /// Serializes this GameDto to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - final _this = this as GameDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is GameDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.type, _this.type) || other.type == _this.type)&&(identical(other.isActive, _this.isActive) || other.isActive == _this.isActive)&&const DeepCollectionEquality().equals(other.metadata, _this.metadata)&&const DeepCollectionEquality().equals(other.prizes, _this.prizes)&&(identical(other.createdAt, _this.createdAt) || other.createdAt == _this.createdAt)&&(identical(other.updatedAt, _this.updatedAt) || other.updatedAt == _this.updatedAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - final _this = this as GameDto; - return Object.hash(runtimeType,_this.id,_this.name,_this.type,_this.isActive,const DeepCollectionEquality().hash(_this.metadata),const DeepCollectionEquality().hash(_this.prizes),_this.createdAt,_this.updatedAt); -} - -@override -String toString() { - final _this = this as GameDto; - return 'GameDto(id: ${_this.id}, name: ${_this.name}, type: ${_this.type}, isActive: ${_this.isActive}, metadata: ${_this.metadata}, prizes: ${_this.prizes}, createdAt: ${_this.createdAt}, updatedAt: ${_this.updatedAt})'; -} - - -} - -/// @nodoc -abstract mixin class $GameDtoCopyWith<$Res> { - factory $GameDtoCopyWith(GameDto value, $Res Function(GameDto) _then) = _$GameDtoCopyWithImpl; -@useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'type') String? type,@JsonKey(name: 'is_active') bool? isActive,@JsonKey(name: 'metadata') Map? metadata,@JsonKey(name: 'prizes') List? prizes,@JsonKey(name: 'created_at') String? createdAt,@JsonKey(name: 'updated_at') String? updatedAt -}); - - - - -} -/// @nodoc -class _$GameDtoCopyWithImpl<$Res> - implements $GameDtoCopyWith<$Res> { - _$GameDtoCopyWithImpl(this._self, this._then); - - final GameDto _self; - final $Res Function(GameDto) _then; - -/// Create a copy of GameDto -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = freezed,Object? type = freezed,Object? isActive = freezed,Object? metadata = freezed,Object? prizes = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,}) { - return _then(GameDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable -as String?,isActive: freezed == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable -as bool?,metadata: freezed == metadata ? _self.metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map?,prizes: freezed == prizes ? _self.prizes : prizes // ignore: cast_nullable_to_non_nullable -as List?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [GameDto]. -extension GameDtoPatterns on GameDto { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _GameDto value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _GameDto() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _GameDto value) $default,){ -final _that = this; -switch (_that) { -case _GameDto(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GameDto value)? $default,){ -final _that = this; -switch (_that) { -case _GameDto() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'type') String? type, @JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'prizes') List? prizes, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _GameDto() when $default != null: -return $default(_that.id,_that.name,_that.type,_that.isActive,_that.metadata,_that.prizes,_that.createdAt,_that.updatedAt);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'type') String? type, @JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'prizes') List? prizes, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt) $default,) {final _that = this; -switch (_that) { -case _GameDto(): -return $default(_that.id,_that.name,_that.type,_that.isActive,_that.metadata,_that.prizes,_that.createdAt,_that.updatedAt);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'type') String? type, @JsonKey(name: 'is_active') bool? isActive, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'prizes') List? prizes, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt)? $default,) {final _that = this; -switch (_that) { -case _GameDto() when $default != null: -return $default(_that.id,_that.name,_that.type,_that.isActive,_that.metadata,_that.prizes,_that.createdAt,_that.updatedAt);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _GameDto extends GameDto { - const _GameDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'name') this.name, @JsonKey(name: 'type') this.type, @JsonKey(name: 'is_active') this.isActive, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'prizes') List? prizes, @JsonKey(name: 'created_at') this.createdAt, @JsonKey(name: 'updated_at') this.updatedAt}): _metadata = metadata,_prizes = prizes,super._(); - factory _GameDto.fromJson(Map json) => _$GameDtoFromJson(json); - -@override@JsonKey(name: 'id') final String? id; -@override@JsonKey(name: 'name') final String? name; -@override@JsonKey(name: 'type') final String? type; -@override@JsonKey(name: 'is_active') final bool? isActive; - final Map? _metadata; -@override@JsonKey(name: 'metadata') Map? get metadata { - final value = _metadata; - if (value == null) return null; - if (_metadata is EqualUnmodifiableMapView) return _metadata; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(value); -} - - final List? _prizes; -@override@JsonKey(name: 'prizes') List? get prizes { - final value = _prizes; - if (value == null) return null; - if (_prizes is EqualUnmodifiableListView) return _prizes; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); -} - -@override@JsonKey(name: 'created_at') final String? createdAt; -@override@JsonKey(name: 'updated_at') final String? updatedAt; - -/// Create a copy of GameDto -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$GameDtoCopyWith<_GameDto> get copyWith => __$GameDtoCopyWithImpl<_GameDto>(this, _$identity); - -@override -Map toJson() { - return _$GameDtoToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _GameDto&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.type, type) || other.type == type)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&const DeepCollectionEquality().equals(other.metadata, _metadata)&&const DeepCollectionEquality().equals(other.prizes, _prizes)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - return Object.hash(runtimeType,id,name,type,isActive,const DeepCollectionEquality().hash(_metadata),const DeepCollectionEquality().hash(_prizes),createdAt,updatedAt); -} - -@override -String toString() { - return 'GameDto(id: $id, name: $name, type: $type, isActive: $isActive, metadata: $metadata, prizes: $prizes, createdAt: $createdAt, updatedAt: $updatedAt)'; -} - - -} - -/// @nodoc -abstract mixin class _$GameDtoCopyWith<$Res> implements $GameDtoCopyWith<$Res> { - factory _$GameDtoCopyWith(_GameDto value, $Res Function(_GameDto) _then) = __$GameDtoCopyWithImpl; -@override @useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'type') String? type,@JsonKey(name: 'is_active') bool? isActive,@JsonKey(name: 'metadata') Map? metadata,@JsonKey(name: 'prizes') List? prizes,@JsonKey(name: 'created_at') String? createdAt,@JsonKey(name: 'updated_at') String? updatedAt -}); - - - - -} -/// @nodoc -class __$GameDtoCopyWithImpl<$Res> - implements _$GameDtoCopyWith<$Res> { - __$GameDtoCopyWithImpl(this._self, this._then); - - final _GameDto _self; - final $Res Function(_GameDto) _then; - -/// Create a copy of GameDto -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = freezed,Object? type = freezed,Object? isActive = freezed,Object? metadata = freezed,Object? prizes = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,}) { - return _then(_GameDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable -as String?,isActive: freezed == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable -as bool?,metadata: freezed == metadata ? _self._metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map?,prizes: freezed == prizes ? _self._prizes : prizes // ignore: cast_nullable_to_non_nullable -as List?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - - -/// @nodoc -mixin _$GamePrizeDto { - -@JsonKey(name: 'id') String? get id;@JsonKey(name: 'game_id') String? get gameId;@JsonKey(name: 'name') String? get name;@JsonKey(name: 'metadata') Map? get metadata;@JsonKey(name: 'created_at') String? get createdAt;@JsonKey(name: 'updated_at') String? get updatedAt; -/// Create a copy of GamePrizeDto -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$GamePrizeDtoCopyWith get copyWith => _$GamePrizeDtoCopyWithImpl(this as GamePrizeDto, _$identity); - - /// Serializes this GamePrizeDto to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - final _this = this as GamePrizeDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is GamePrizeDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.gameId, _this.gameId) || other.gameId == _this.gameId)&&(identical(other.name, _this.name) || other.name == _this.name)&&const DeepCollectionEquality().equals(other.metadata, _this.metadata)&&(identical(other.createdAt, _this.createdAt) || other.createdAt == _this.createdAt)&&(identical(other.updatedAt, _this.updatedAt) || other.updatedAt == _this.updatedAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - final _this = this as GamePrizeDto; - return Object.hash(runtimeType,_this.id,_this.gameId,_this.name,const DeepCollectionEquality().hash(_this.metadata),_this.createdAt,_this.updatedAt); -} - -@override -String toString() { - final _this = this as GamePrizeDto; - return 'GamePrizeDto(id: ${_this.id}, gameId: ${_this.gameId}, name: ${_this.name}, metadata: ${_this.metadata}, createdAt: ${_this.createdAt}, updatedAt: ${_this.updatedAt})'; -} - - -} - -/// @nodoc -abstract mixin class $GamePrizeDtoCopyWith<$Res> { - factory $GamePrizeDtoCopyWith(GamePrizeDto value, $Res Function(GamePrizeDto) _then) = _$GamePrizeDtoCopyWithImpl; -@useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'game_id') String? gameId,@JsonKey(name: 'name') String? name,@JsonKey(name: 'metadata') Map? metadata,@JsonKey(name: 'created_at') String? createdAt,@JsonKey(name: 'updated_at') String? updatedAt -}); - - - - -} -/// @nodoc -class _$GamePrizeDtoCopyWithImpl<$Res> - implements $GamePrizeDtoCopyWith<$Res> { - _$GamePrizeDtoCopyWithImpl(this._self, this._then); - - final GamePrizeDto _self; - final $Res Function(GamePrizeDto) _then; - -/// Create a copy of GamePrizeDto -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? gameId = freezed,Object? name = freezed,Object? metadata = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,}) { - return _then(GamePrizeDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,gameId: freezed == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?,metadata: freezed == metadata ? _self.metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [GamePrizeDto]. -extension GamePrizeDtoPatterns on GamePrizeDto { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _GamePrizeDto value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _GamePrizeDto() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _GamePrizeDto value) $default,){ -final _that = this; -switch (_that) { -case _GamePrizeDto(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GamePrizeDto value)? $default,){ -final _that = this; -switch (_that) { -case _GamePrizeDto() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'name') String? name, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _GamePrizeDto() when $default != null: -return $default(_that.id,_that.gameId,_that.name,_that.metadata,_that.createdAt,_that.updatedAt);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'name') String? name, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt) $default,) {final _that = this; -switch (_that) { -case _GamePrizeDto(): -return $default(_that.id,_that.gameId,_that.name,_that.metadata,_that.createdAt,_that.updatedAt);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'name') String? name, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'updated_at') String? updatedAt)? $default,) {final _that = this; -switch (_that) { -case _GamePrizeDto() when $default != null: -return $default(_that.id,_that.gameId,_that.name,_that.metadata,_that.createdAt,_that.updatedAt);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _GamePrizeDto extends GamePrizeDto { - const _GamePrizeDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'game_id') this.gameId, @JsonKey(name: 'name') this.name, @JsonKey(name: 'metadata') Map? metadata, @JsonKey(name: 'created_at') this.createdAt, @JsonKey(name: 'updated_at') this.updatedAt}): _metadata = metadata,super._(); - factory _GamePrizeDto.fromJson(Map json) => _$GamePrizeDtoFromJson(json); - -@override@JsonKey(name: 'id') final String? id; -@override@JsonKey(name: 'game_id') final String? gameId; -@override@JsonKey(name: 'name') final String? name; - final Map? _metadata; -@override@JsonKey(name: 'metadata') Map? get metadata { - final value = _metadata; - if (value == null) return null; - if (_metadata is EqualUnmodifiableMapView) return _metadata; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(value); -} - -@override@JsonKey(name: 'created_at') final String? createdAt; -@override@JsonKey(name: 'updated_at') final String? updatedAt; - -/// Create a copy of GamePrizeDto -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$GamePrizeDtoCopyWith<_GamePrizeDto> get copyWith => __$GamePrizeDtoCopyWithImpl<_GamePrizeDto>(this, _$identity); - -@override -Map toJson() { - return _$GamePrizeDtoToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _GamePrizeDto&&(identical(other.id, id) || other.id == id)&&(identical(other.gameId, gameId) || other.gameId == gameId)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.metadata, _metadata)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - return Object.hash(runtimeType,id,gameId,name,const DeepCollectionEquality().hash(_metadata),createdAt,updatedAt); -} - -@override -String toString() { - return 'GamePrizeDto(id: $id, gameId: $gameId, name: $name, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; -} - - -} - -/// @nodoc -abstract mixin class _$GamePrizeDtoCopyWith<$Res> implements $GamePrizeDtoCopyWith<$Res> { - factory _$GamePrizeDtoCopyWith(_GamePrizeDto value, $Res Function(_GamePrizeDto) _then) = __$GamePrizeDtoCopyWithImpl; -@override @useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'game_id') String? gameId,@JsonKey(name: 'name') String? name,@JsonKey(name: 'metadata') Map? metadata,@JsonKey(name: 'created_at') String? createdAt,@JsonKey(name: 'updated_at') String? updatedAt -}); - - - - -} -/// @nodoc -class __$GamePrizeDtoCopyWithImpl<$Res> - implements _$GamePrizeDtoCopyWith<$Res> { - __$GamePrizeDtoCopyWithImpl(this._self, this._then); - - final _GamePrizeDto _self; - final $Res Function(_GamePrizeDto) _then; - -/// Create a copy of GamePrizeDto -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? gameId = freezed,Object? name = freezed,Object? metadata = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,}) { - return _then(_GamePrizeDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,gameId: freezed == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?,metadata: freezed == metadata ? _self._metadata : metadata // ignore: cast_nullable_to_non_nullable -as Map?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - - -/// @nodoc -mixin _$SpinResultDto { - -@JsonKey(name: 'game_play') GamePlayDto? get gamePlay;@JsonKey(name: 'prize_won') PrizeWonDto? get prizeWon;@JsonKey(name: 'coins_remaining') int? get coinsRemaining; -/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$SpinResultDtoCopyWith get copyWith => _$SpinResultDtoCopyWithImpl(this as SpinResultDto, _$identity); - - /// Serializes this SpinResultDto to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - final _this = this as SpinResultDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is SpinResultDto&&(identical(other.gamePlay, _this.gamePlay) || other.gamePlay == _this.gamePlay)&&(identical(other.prizeWon, _this.prizeWon) || other.prizeWon == _this.prizeWon)&&(identical(other.coinsRemaining, _this.coinsRemaining) || other.coinsRemaining == _this.coinsRemaining)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - final _this = this as SpinResultDto; - return Object.hash(runtimeType,_this.gamePlay,_this.prizeWon,_this.coinsRemaining); -} - -@override -String toString() { - final _this = this as SpinResultDto; - return 'SpinResultDto(gamePlay: ${_this.gamePlay}, prizeWon: ${_this.prizeWon}, coinsRemaining: ${_this.coinsRemaining})'; -} - - -} - -/// @nodoc -abstract mixin class $SpinResultDtoCopyWith<$Res> { - factory $SpinResultDtoCopyWith(SpinResultDto value, $Res Function(SpinResultDto) _then) = _$SpinResultDtoCopyWithImpl; -@useResult -$Res call({ -@JsonKey(name: 'game_play') GamePlayDto? gamePlay,@JsonKey(name: 'prize_won') PrizeWonDto? prizeWon,@JsonKey(name: 'coins_remaining') int? coinsRemaining -}); - - -$GamePlayDtoCopyWith<$Res>? get gamePlay;$PrizeWonDtoCopyWith<$Res>? get prizeWon; - -} -/// @nodoc -class _$SpinResultDtoCopyWithImpl<$Res> - implements $SpinResultDtoCopyWith<$Res> { - _$SpinResultDtoCopyWithImpl(this._self, this._then); - - final SpinResultDto _self; - final $Res Function(SpinResultDto) _then; - -/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? gamePlay = freezed,Object? prizeWon = freezed,Object? coinsRemaining = freezed,}) { - return _then(SpinResultDto( -gamePlay: freezed == gamePlay ? _self.gamePlay : gamePlay // ignore: cast_nullable_to_non_nullable -as GamePlayDto?,prizeWon: freezed == prizeWon ? _self.prizeWon : prizeWon // ignore: cast_nullable_to_non_nullable -as PrizeWonDto?,coinsRemaining: freezed == coinsRemaining ? _self.coinsRemaining : coinsRemaining // ignore: cast_nullable_to_non_nullable -as int?, - )); -} -/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$GamePlayDtoCopyWith<$Res>? get gamePlay { - if (_self.gamePlay == null) { - return null; - } - - return $GamePlayDtoCopyWith<$Res>(_self.gamePlay!, (value) { - return _then(_self.copyWith(gamePlay: value)); - }); -}/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$PrizeWonDtoCopyWith<$Res>? get prizeWon { - if (_self.prizeWon == null) { - return null; - } - - return $PrizeWonDtoCopyWith<$Res>(_self.prizeWon!, (value) { - return _then(_self.copyWith(prizeWon: value)); - }); -} -} - - -/// Adds pattern-matching-related methods to [SpinResultDto]. -extension SpinResultDtoPatterns on SpinResultDto { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _SpinResultDto value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _SpinResultDto() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _SpinResultDto value) $default,){ -final _that = this; -switch (_that) { -case _SpinResultDto(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SpinResultDto value)? $default,){ -final _that = this; -switch (_that) { -case _SpinResultDto() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'game_play') GamePlayDto? gamePlay, @JsonKey(name: 'prize_won') PrizeWonDto? prizeWon, @JsonKey(name: 'coins_remaining') int? coinsRemaining)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _SpinResultDto() when $default != null: -return $default(_that.gamePlay,_that.prizeWon,_that.coinsRemaining);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'game_play') GamePlayDto? gamePlay, @JsonKey(name: 'prize_won') PrizeWonDto? prizeWon, @JsonKey(name: 'coins_remaining') int? coinsRemaining) $default,) {final _that = this; -switch (_that) { -case _SpinResultDto(): -return $default(_that.gamePlay,_that.prizeWon,_that.coinsRemaining);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'game_play') GamePlayDto? gamePlay, @JsonKey(name: 'prize_won') PrizeWonDto? prizeWon, @JsonKey(name: 'coins_remaining') int? coinsRemaining)? $default,) {final _that = this; -switch (_that) { -case _SpinResultDto() when $default != null: -return $default(_that.gamePlay,_that.prizeWon,_that.coinsRemaining);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _SpinResultDto extends SpinResultDto { - const _SpinResultDto({@JsonKey(name: 'game_play') this.gamePlay, @JsonKey(name: 'prize_won') this.prizeWon, @JsonKey(name: 'coins_remaining') this.coinsRemaining}): super._(); - factory _SpinResultDto.fromJson(Map json) => _$SpinResultDtoFromJson(json); - -@override@JsonKey(name: 'game_play') final GamePlayDto? gamePlay; -@override@JsonKey(name: 'prize_won') final PrizeWonDto? prizeWon; -@override@JsonKey(name: 'coins_remaining') final int? coinsRemaining; - -/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$SpinResultDtoCopyWith<_SpinResultDto> get copyWith => __$SpinResultDtoCopyWithImpl<_SpinResultDto>(this, _$identity); - -@override -Map toJson() { - return _$SpinResultDtoToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _SpinResultDto&&(identical(other.gamePlay, gamePlay) || other.gamePlay == gamePlay)&&(identical(other.prizeWon, prizeWon) || other.prizeWon == prizeWon)&&(identical(other.coinsRemaining, coinsRemaining) || other.coinsRemaining == coinsRemaining)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - return Object.hash(runtimeType,gamePlay,prizeWon,coinsRemaining); -} - -@override -String toString() { - return 'SpinResultDto(gamePlay: $gamePlay, prizeWon: $prizeWon, coinsRemaining: $coinsRemaining)'; -} - - -} - -/// @nodoc -abstract mixin class _$SpinResultDtoCopyWith<$Res> implements $SpinResultDtoCopyWith<$Res> { - factory _$SpinResultDtoCopyWith(_SpinResultDto value, $Res Function(_SpinResultDto) _then) = __$SpinResultDtoCopyWithImpl; -@override @useResult -$Res call({ -@JsonKey(name: 'game_play') GamePlayDto? gamePlay,@JsonKey(name: 'prize_won') PrizeWonDto? prizeWon,@JsonKey(name: 'coins_remaining') int? coinsRemaining -}); - - -@override $GamePlayDtoCopyWith<$Res>? get gamePlay;@override $PrizeWonDtoCopyWith<$Res>? get prizeWon; - -} -/// @nodoc -class __$SpinResultDtoCopyWithImpl<$Res> - implements _$SpinResultDtoCopyWith<$Res> { - __$SpinResultDtoCopyWithImpl(this._self, this._then); - - final _SpinResultDto _self; - final $Res Function(_SpinResultDto) _then; - -/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? gamePlay = freezed,Object? prizeWon = freezed,Object? coinsRemaining = freezed,}) { - return _then(_SpinResultDto( -gamePlay: freezed == gamePlay ? _self.gamePlay : gamePlay // ignore: cast_nullable_to_non_nullable -as GamePlayDto?,prizeWon: freezed == prizeWon ? _self.prizeWon : prizeWon // ignore: cast_nullable_to_non_nullable -as PrizeWonDto?,coinsRemaining: freezed == coinsRemaining ? _self.coinsRemaining : coinsRemaining // ignore: cast_nullable_to_non_nullable -as int?, - )); -} - -/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$GamePlayDtoCopyWith<$Res>? get gamePlay { - if (_self.gamePlay == null) { - return null; - } - - return $GamePlayDtoCopyWith<$Res>(_self.gamePlay!, (value) { - return _then(_self.copyWith(gamePlay: value)); - }); -}/// Create a copy of SpinResultDto -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$PrizeWonDtoCopyWith<$Res>? get prizeWon { - if (_self.prizeWon == null) { - return null; - } - - return $PrizeWonDtoCopyWith<$Res>(_self.prizeWon!, (value) { - return _then(_self.copyWith(prizeWon: value)); - }); -} -} - - -/// @nodoc -mixin _$GamePlayDto { - -@JsonKey(name: 'id') String? get id;@JsonKey(name: 'game_id') String? get gameId;@JsonKey(name: 'coins_used') int? get coinsUsed;@JsonKey(name: 'created_at') String? get createdAt; -/// Create a copy of GamePlayDto -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$GamePlayDtoCopyWith get copyWith => _$GamePlayDtoCopyWithImpl(this as GamePlayDto, _$identity); - - /// Serializes this GamePlayDto to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - final _this = this as GamePlayDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is GamePlayDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.gameId, _this.gameId) || other.gameId == _this.gameId)&&(identical(other.coinsUsed, _this.coinsUsed) || other.coinsUsed == _this.coinsUsed)&&(identical(other.createdAt, _this.createdAt) || other.createdAt == _this.createdAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - final _this = this as GamePlayDto; - return Object.hash(runtimeType,_this.id,_this.gameId,_this.coinsUsed,_this.createdAt); -} - -@override -String toString() { - final _this = this as GamePlayDto; - return 'GamePlayDto(id: ${_this.id}, gameId: ${_this.gameId}, coinsUsed: ${_this.coinsUsed}, createdAt: ${_this.createdAt})'; -} - - -} - -/// @nodoc -abstract mixin class $GamePlayDtoCopyWith<$Res> { - factory $GamePlayDtoCopyWith(GamePlayDto value, $Res Function(GamePlayDto) _then) = _$GamePlayDtoCopyWithImpl; -@useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'game_id') String? gameId,@JsonKey(name: 'coins_used') int? coinsUsed,@JsonKey(name: 'created_at') String? createdAt -}); - - - - -} -/// @nodoc -class _$GamePlayDtoCopyWithImpl<$Res> - implements $GamePlayDtoCopyWith<$Res> { - _$GamePlayDtoCopyWithImpl(this._self, this._then); - - final GamePlayDto _self; - final $Res Function(GamePlayDto) _then; - -/// Create a copy of GamePlayDto -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? gameId = freezed,Object? coinsUsed = freezed,Object? createdAt = freezed,}) { - return _then(GamePlayDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,gameId: freezed == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String?,coinsUsed: freezed == coinsUsed ? _self.coinsUsed : coinsUsed // ignore: cast_nullable_to_non_nullable -as int?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [GamePlayDto]. -extension GamePlayDtoPatterns on GamePlayDto { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _GamePlayDto value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _GamePlayDto() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _GamePlayDto value) $default,){ -final _that = this; -switch (_that) { -case _GamePlayDto(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GamePlayDto value)? $default,){ -final _that = this; -switch (_that) { -case _GamePlayDto() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'coins_used') int? coinsUsed, @JsonKey(name: 'created_at') String? createdAt)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _GamePlayDto() when $default != null: -return $default(_that.id,_that.gameId,_that.coinsUsed,_that.createdAt);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'coins_used') int? coinsUsed, @JsonKey(name: 'created_at') String? createdAt) $default,) {final _that = this; -switch (_that) { -case _GamePlayDto(): -return $default(_that.id,_that.gameId,_that.coinsUsed,_that.createdAt);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'game_id') String? gameId, @JsonKey(name: 'coins_used') int? coinsUsed, @JsonKey(name: 'created_at') String? createdAt)? $default,) {final _that = this; -switch (_that) { -case _GamePlayDto() when $default != null: -return $default(_that.id,_that.gameId,_that.coinsUsed,_that.createdAt);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _GamePlayDto implements GamePlayDto { - const _GamePlayDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'game_id') this.gameId, @JsonKey(name: 'coins_used') this.coinsUsed, @JsonKey(name: 'created_at') this.createdAt}); - factory _GamePlayDto.fromJson(Map json) => _$GamePlayDtoFromJson(json); - -@override@JsonKey(name: 'id') final String? id; -@override@JsonKey(name: 'game_id') final String? gameId; -@override@JsonKey(name: 'coins_used') final int? coinsUsed; -@override@JsonKey(name: 'created_at') final String? createdAt; - -/// Create a copy of GamePlayDto -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$GamePlayDtoCopyWith<_GamePlayDto> get copyWith => __$GamePlayDtoCopyWithImpl<_GamePlayDto>(this, _$identity); - -@override -Map toJson() { - return _$GamePlayDtoToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _GamePlayDto&&(identical(other.id, id) || other.id == id)&&(identical(other.gameId, gameId) || other.gameId == gameId)&&(identical(other.coinsUsed, coinsUsed) || other.coinsUsed == coinsUsed)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - return Object.hash(runtimeType,id,gameId,coinsUsed,createdAt); -} - -@override -String toString() { - return 'GamePlayDto(id: $id, gameId: $gameId, coinsUsed: $coinsUsed, createdAt: $createdAt)'; -} - - -} - -/// @nodoc -abstract mixin class _$GamePlayDtoCopyWith<$Res> implements $GamePlayDtoCopyWith<$Res> { - factory _$GamePlayDtoCopyWith(_GamePlayDto value, $Res Function(_GamePlayDto) _then) = __$GamePlayDtoCopyWithImpl; -@override @useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'game_id') String? gameId,@JsonKey(name: 'coins_used') int? coinsUsed,@JsonKey(name: 'created_at') String? createdAt -}); - - - - -} -/// @nodoc -class __$GamePlayDtoCopyWithImpl<$Res> - implements _$GamePlayDtoCopyWith<$Res> { - __$GamePlayDtoCopyWithImpl(this._self, this._then); - - final _GamePlayDto _self; - final $Res Function(_GamePlayDto) _then; - -/// Create a copy of GamePlayDto -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? gameId = freezed,Object? coinsUsed = freezed,Object? createdAt = freezed,}) { - return _then(_GamePlayDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,gameId: freezed == gameId ? _self.gameId : gameId // ignore: cast_nullable_to_non_nullable -as String?,coinsUsed: freezed == coinsUsed ? _self.coinsUsed : coinsUsed // ignore: cast_nullable_to_non_nullable -as int?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - - -/// @nodoc -mixin _$PrizeWonDto { - -@JsonKey(name: 'id') String? get id;@JsonKey(name: 'name') String? get name; -/// Create a copy of PrizeWonDto -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$PrizeWonDtoCopyWith get copyWith => _$PrizeWonDtoCopyWithImpl(this as PrizeWonDto, _$identity); - - /// Serializes this PrizeWonDto to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - final _this = this as PrizeWonDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is PrizeWonDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - final _this = this as PrizeWonDto; - return Object.hash(runtimeType,_this.id,_this.name); -} - -@override -String toString() { - final _this = this as PrizeWonDto; - return 'PrizeWonDto(id: ${_this.id}, name: ${_this.name})'; -} - - -} - -/// @nodoc -abstract mixin class $PrizeWonDtoCopyWith<$Res> { - factory $PrizeWonDtoCopyWith(PrizeWonDto value, $Res Function(PrizeWonDto) _then) = _$PrizeWonDtoCopyWithImpl; -@useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name -}); - - - - -} -/// @nodoc -class _$PrizeWonDtoCopyWithImpl<$Res> - implements $PrizeWonDtoCopyWith<$Res> { - _$PrizeWonDtoCopyWithImpl(this._self, this._then); - - final PrizeWonDto _self; - final $Res Function(PrizeWonDto) _then; - -/// Create a copy of PrizeWonDto -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = freezed,}) { - return _then(PrizeWonDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [PrizeWonDto]. -extension PrizeWonDtoPatterns on PrizeWonDto { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _PrizeWonDto value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _PrizeWonDto() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _PrizeWonDto value) $default,){ -final _that = this; -switch (_that) { -case _PrizeWonDto(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PrizeWonDto value)? $default,){ -final _that = this; -switch (_that) { -case _PrizeWonDto() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _PrizeWonDto() when $default != null: -return $default(_that.id,_that.name);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name) $default,) {final _that = this; -switch (_that) { -case _PrizeWonDto(): -return $default(_that.id,_that.name);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name)? $default,) {final _that = this; -switch (_that) { -case _PrizeWonDto() when $default != null: -return $default(_that.id,_that.name);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _PrizeWonDto implements PrizeWonDto { - const _PrizeWonDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'name') this.name}); - factory _PrizeWonDto.fromJson(Map json) => _$PrizeWonDtoFromJson(json); - -@override@JsonKey(name: 'id') final String? id; -@override@JsonKey(name: 'name') final String? name; - -/// Create a copy of PrizeWonDto -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$PrizeWonDtoCopyWith<_PrizeWonDto> get copyWith => __$PrizeWonDtoCopyWithImpl<_PrizeWonDto>(this, _$identity); - -@override -Map toJson() { - return _$PrizeWonDtoToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PrizeWonDto&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - return Object.hash(runtimeType,id,name); -} - -@override -String toString() { - return 'PrizeWonDto(id: $id, name: $name)'; -} - - -} - -/// @nodoc -abstract mixin class _$PrizeWonDtoCopyWith<$Res> implements $PrizeWonDtoCopyWith<$Res> { - factory _$PrizeWonDtoCopyWith(_PrizeWonDto value, $Res Function(_PrizeWonDto) _then) = __$PrizeWonDtoCopyWithImpl; -@override @useResult -$Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name -}); - - - - -} -/// @nodoc -class __$PrizeWonDtoCopyWithImpl<$Res> - implements _$PrizeWonDtoCopyWith<$Res> { - __$PrizeWonDtoCopyWithImpl(this._self, this._then); - - final _PrizeWonDto _self; - final $Res Function(_PrizeWonDto) _then; - -/// Create a copy of PrizeWonDto -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = freezed,}) { - return _then(_PrizeWonDto( -id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - -// dart format on diff --git a/lib/infrastructure/game/game_dtos.g.dart b/lib/infrastructure/game/game_dtos.g.dart deleted file mode 100644 index c86b986..0000000 --- a/lib/infrastructure/game/game_dtos.g.dart +++ /dev/null @@ -1,90 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'game_dtos.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_GameDto _$GameDtoFromJson(Map json) => _GameDto( - id: json['id'] as String?, - name: json['name'] as String?, - type: json['type'] as String?, - isActive: json['is_active'] as bool?, - metadata: json['metadata'] as Map?, - prizes: (json['prizes'] as List?) - ?.map((e) => GamePrizeDto.fromJson(e as Map)) - .toList(), - createdAt: json['created_at'] as String?, - updatedAt: json['updated_at'] as String?, -); - -Map _$GameDtoToJson(_GameDto instance) => { - 'id': instance.id, - 'name': instance.name, - 'type': instance.type, - 'is_active': instance.isActive, - 'metadata': instance.metadata, - 'prizes': instance.prizes, - 'created_at': instance.createdAt, - 'updated_at': instance.updatedAt, -}; - -_GamePrizeDto _$GamePrizeDtoFromJson(Map json) => - _GamePrizeDto( - id: json['id'] as String?, - gameId: json['game_id'] as String?, - name: json['name'] as String?, - metadata: json['metadata'] as Map?, - createdAt: json['created_at'] as String?, - updatedAt: json['updated_at'] as String?, - ); - -Map _$GamePrizeDtoToJson(_GamePrizeDto instance) => - { - 'id': instance.id, - 'game_id': instance.gameId, - 'name': instance.name, - 'metadata': instance.metadata, - 'created_at': instance.createdAt, - 'updated_at': instance.updatedAt, - }; - -_SpinResultDto _$SpinResultDtoFromJson(Map json) => - _SpinResultDto( - gamePlay: json['game_play'] == null - ? null - : GamePlayDto.fromJson(json['game_play'] as Map), - prizeWon: json['prize_won'] == null - ? null - : PrizeWonDto.fromJson(json['prize_won'] as Map), - coinsRemaining: (json['coins_remaining'] as num?)?.toInt(), - ); - -Map _$SpinResultDtoToJson(_SpinResultDto instance) => - { - 'game_play': instance.gamePlay, - 'prize_won': instance.prizeWon, - 'coins_remaining': instance.coinsRemaining, - }; - -_GamePlayDto _$GamePlayDtoFromJson(Map json) => _GamePlayDto( - id: json['id'] as String?, - gameId: json['game_id'] as String?, - coinsUsed: (json['coins_used'] as num?)?.toInt(), - createdAt: json['created_at'] as String?, -); - -Map _$GamePlayDtoToJson(_GamePlayDto instance) => - { - 'id': instance.id, - 'game_id': instance.gameId, - 'coins_used': instance.coinsUsed, - 'created_at': instance.createdAt, - }; - -_PrizeWonDto _$PrizeWonDtoFromJson(Map json) => - _PrizeWonDto(id: json['id'] as String?, name: json['name'] as String?); - -Map _$PrizeWonDtoToJson(_PrizeWonDto instance) => - {'id': instance.id, 'name': instance.name}; diff --git a/lib/infrastructure/game/repositories/game_repository.dart b/lib/infrastructure/game/repositories/game_repository.dart deleted file mode 100644 index 89a50d9..0000000 --- a/lib/infrastructure/game/repositories/game_repository.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:developer'; - -import 'package:dartz/dartz.dart'; -import 'package:injectable/injectable.dart'; - -import '../../../domain/game/game.dart'; -import '../datasources/remote_data_provider.dart'; - -@Injectable(as: IGameRepository) -class GameRepository implements IGameRepository { - final GameRemoteDataProvider _remoteDataProvider; - - final String _logName = 'GameRepository'; - - GameRepository(this._remoteDataProvider); - - @override - Future> ferrisWheel() async { - try { - final result = await _remoteDataProvider.ferrisWheel(); - - if (result.hasError) { - return left(result.error!); - } - - final data = result.data!.toDomain(); - - return right(data); - } catch (e, s) { - log('ferrisWheel', name: _logName, error: e, stackTrace: s); - return left(const GameFailure.unexpectedError()); - } - } - - @override - Future> spin({required String gameId}) async { - try { - final result = await _remoteDataProvider.spin(gameId: gameId); - - if (result.hasError) { - return left(result.error!); - } - - return right(result.data!.toDomain()); - } catch (e, s) { - log('spin', name: _logName, error: e, stackTrace: s); - return left(const GameFailure.unexpectedError()); - } - } -} diff --git a/lib/infrastructure/wallet/datasources/voucher_remote_data_provider.dart b/lib/infrastructure/wallet/datasources/voucher_remote_data_provider.dart new file mode 100644 index 0000000..31a9db8 --- /dev/null +++ b/lib/infrastructure/wallet/datasources/voucher_remote_data_provider.dart @@ -0,0 +1,101 @@ +import 'dart:developer'; + +import 'package:data_channel/data_channel.dart'; +import 'package:dio/dio.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../common/api/api_client.dart'; +import '../../../common/api/api_failure.dart'; +import '../../../common/api/interceptors/sensitive_aware_log_interceptor.dart'; +import '../../../common/function/app_function.dart'; +import '../../../common/url/api_path.dart'; +import '../../../domain/wallet/wallet.dart'; +import '../wallet_dtos.dart'; +import '../wallet_response.dart'; + +/// Katalog, tukar, dan riwayat voucher (§9). +@injectable +class VoucherRemoteDataProvider { + final ApiClient _apiClient; + final String _logName = 'VoucherRemoteDataProvider'; + + VoucherRemoteDataProvider(this._apiClient); + + Future>> fetchVouchers() => _request( + 'fetchVouchers', + () => _apiClient.get(ApiPath.vouchers, headers: getAuthorizationHeader()), + (response) => ((response.data as Map)['data'] as List? ?? []) + .map((e) => VoucherDto.fromJson(Map.from(e as Map))) + .toList(), + ); + + Future> redeem({ + required String voucherId, + required String pin, + required String idempotencyKey, + }) => _request( + 'redeem', + () => _apiClient.post( + ApiPath.voucherRedeem(voucherId), + headers: {...getAuthorizationHeader(), 'Idempotency-Key': idempotencyKey}, + data: {'pin': pin}, + extra: {kSensitiveRequest: true}, + ), + (response) => VoucherRedemptionDto.fromJson(walletDataOf(response)), + ); + + Future> fetchRedemptions({ + required int page, + required int limit, + }) => _request( + 'fetchRedemptions', + () => _apiClient.get( + ApiPath.voucherRedemptions, + headers: getAuthorizationHeader(), + params: {'page': page, 'limit': limit}, + ), + _parseRedemptionPage, + ); + + /// `data` bisa berupa list langsung dengan `pagination` di envelope, atau + /// objek `{ data, pagination }`. + VoucherRedemptionPageDto _parseRedemptionPage(Response response) { + final body = response.data as Map; + final payload = body['data']; + final rawItems = payload is Map ? payload['data'] : payload; + final rawPagination = payload is Map + ? payload['pagination'] + : body['pagination']; + + return VoucherRedemptionPageDto( + items: (rawItems as List? ?? []) + .map( + (e) => VoucherRedemptionDto.fromJson( + Map.from(e as Map), + ), + ) + .toList(), + pagination: rawPagination is Map + ? WalletPaginationDto.fromJson( + Map.from(rawPagination), + ) + : const WalletPaginationDto(), + ); + } + + Future> _request( + String name, + Future Function() call, + T Function(Response response) parse, + ) async { + try { + final response = await call(); + final failure = parseWalletFailure(response); + if (failure != null) return DC.error(failure); + return DC.data(parse(response)); + } on ApiFailure catch (e, s) { + log(name, name: _logName, error: e, stackTrace: s); + return DC.error(WalletFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/wallet/datasources/wallet_remote_data_provider.dart b/lib/infrastructure/wallet/datasources/wallet_remote_data_provider.dart index 1359bd7..22ffcad 100644 --- a/lib/infrastructure/wallet/datasources/wallet_remote_data_provider.dart +++ b/lib/infrastructure/wallet/datasources/wallet_remote_data_provider.dart @@ -60,19 +60,6 @@ class WalletRemoteDataProvider { (response) => WalletExpiringDto.fromJson(walletDataOf(response)), ); - Future> createPaymentCode({ - required String pin, - }) => _request( - 'createPaymentCode', - () => _apiClient.post( - ApiPath.walletPaymentCode, - headers: getAuthorizationHeader(), - data: {'pin': pin}, - extra: {kSensitiveRequest: true}, - ), - (response) => PaymentCodeDto.fromJson(walletDataOf(response)), - ); - Future> previewExchange({ required int coins, }) => _request( diff --git a/lib/infrastructure/wallet/dto/customer_order_dto.dart b/lib/infrastructure/wallet/dto/customer_order_dto.dart index af44210..3411274 100644 --- a/lib/infrastructure/wallet/dto/customer_order_dto.dart +++ b/lib/infrastructure/wallet/dto/customer_order_dto.dart @@ -100,7 +100,6 @@ abstract class CustomerOrderPaymentDto with _$CustomerOrderPaymentDto { @JsonKey(name: 'method_type') String? methodType, @JsonKey(name: 'amount') int? amount, @JsonKey(name: 'refund_amount') int? refundAmount, - @JsonKey(name: 'points_used') int? pointsUsed, }) = _CustomerOrderPaymentDto; factory CustomerOrderPaymentDto.fromJson(Map json) => @@ -114,7 +113,6 @@ abstract class CustomerOrderPaymentDto with _$CustomerOrderPaymentDto { methodType: methodType ?? '', amount: amount ?? 0, refundAmount: refundAmount ?? 0, - pointsUsed: pointsUsed, ); } diff --git a/lib/infrastructure/wallet/dto/outlet_dto.dart b/lib/infrastructure/wallet/dto/outlet_dto.dart index d79b5eb..824ce6b 100644 --- a/lib/infrastructure/wallet/dto/outlet_dto.dart +++ b/lib/infrastructure/wallet/dto/outlet_dto.dart @@ -6,7 +6,6 @@ abstract class OutletDto with _$OutletDto { @JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'address') String? address, - @JsonKey(name: 'accepts_point_payment') bool? acceptsPointPayment, @JsonKey(name: 'earns_points') bool? earnsPoints, @JsonKey(name: 'earns_coins') bool? earnsCoins, }) = _OutletDto; @@ -20,7 +19,6 @@ abstract class OutletDto with _$OutletDto { id: id ?? '', name: name ?? '', address: (address?.trim().isEmpty ?? true) ? null : address!.trim(), - acceptsPointPayment: acceptsPointPayment ?? false, earnsPoints: earnsPoints ?? false, earnsCoins: earnsCoins ?? false, ); diff --git a/lib/infrastructure/wallet/dto/payment_code_dto.dart b/lib/infrastructure/wallet/dto/payment_code_dto.dart deleted file mode 100644 index 80c19f6..0000000 --- a/lib/infrastructure/wallet/dto/payment_code_dto.dart +++ /dev/null @@ -1,24 +0,0 @@ -part of '../wallet_dtos.dart'; - -@freezed -abstract class PaymentCodeDto with _$PaymentCodeDto { - const factory PaymentCodeDto({ - @JsonKey(name: 'code') String? code, - @JsonKey(name: 'qr_payload') String? qrPayload, - @JsonKey(name: 'expires_at') String? expiresAt, - }) = _PaymentCodeDto; - - factory PaymentCodeDto.fromJson(Map json) => - _$PaymentCodeDtoFromJson(json); - - const PaymentCodeDto._(); - - PaymentCode toDomain() => PaymentCode( - code: code ?? '', - qrPayload: qrPayload ?? '', - // Kode berlaku 2 menit; dipakai hanya bila server tidak mengirim expires_at. - expiresAt: - _parseDateTime(expiresAt) ?? - DateTime.now().add(const Duration(minutes: 2)), - ); -} diff --git a/lib/infrastructure/wallet/dto/voucher_dto.dart b/lib/infrastructure/wallet/dto/voucher_dto.dart new file mode 100644 index 0000000..3ba7c69 --- /dev/null +++ b/lib/infrastructure/wallet/dto/voucher_dto.dart @@ -0,0 +1,103 @@ +part of '../wallet_dtos.dart'; + +/// `terms` belum dibaca: bentuknya masih disepakati dengan tim backoffice, +/// sementara itu yang ditampilkan `description` saja (§9.1). +@freezed +abstract class VoucherDto with _$VoucherDto { + const factory VoucherDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'description') String? description, + @JsonKey(name: 'image_url') String? imageUrl, + @JsonKey(name: 'voucher_type') String? voucherType, + @JsonKey(name: 'face_value') int? faceValue, + @JsonKey(name: 'point_cost') int? pointCost, + @JsonKey(name: 'max_per_customer') int? maxPerCustomer, + @JsonKey(name: 'valid_until') String? validUntil, + @JsonKey(name: 'available') int? available, + }) = _VoucherDto; + + factory VoucherDto.fromJson(Map json) => + _$VoucherDtoFromJson(json); + + const VoucherDto._(); + + Voucher toDomain() => Voucher( + id: id ?? '', + name: name ?? '', + description: (description?.trim().isEmpty ?? true) ? null : description, + imageUrl: (imageUrl?.isEmpty ?? true) ? null : imageUrl, + type: VoucherType.fromApi(voucherType), + faceValue: faceValue ?? 0, + pointCost: pointCost ?? 0, + maxPerCustomer: maxPerCustomer, + validUntil: _parseDateTime(validUntil), + available: available, + ); +} + +/// Item "Voucher saya" dan response tukar voucher; [pointBalance] dan +/// [replayed] hanya ada di response tukar. +@freezed +abstract class VoucherRedemptionDto with _$VoucherRedemptionDto { + const factory VoucherRedemptionDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'voucher_id') String? voucherId, + @JsonKey(name: 'voucher_name') String? voucherName, + @JsonKey(name: 'voucher_image_url') String? voucherImageUrl, + @JsonKey(name: 'voucher_type') String? voucherType, + @JsonKey(name: 'status') String? status, + @JsonKey(name: 'face_value') int? faceValue, + @JsonKey(name: 'point_cost') int? pointCost, + @JsonKey(name: 'code') String? code, + @JsonKey(name: 'code_expires_at') String? codeExpiresAt, + @JsonKey(name: 'completed_at') String? completedAt, + @JsonKey(name: 'created_at') String? createdAt, + @JsonKey(name: 'point_balance') int? pointBalance, + @JsonKey(name: 'replayed') bool? replayed, + }) = _VoucherRedemptionDto; + + factory VoucherRedemptionDto.fromJson(Map json) => + _$VoucherRedemptionDtoFromJson(json); + + const VoucherRedemptionDto._(); + + VoucherRedemption toDomain() => VoucherRedemption( + id: id ?? '', + voucherId: voucherId ?? '', + voucherName: voucherName ?? '', + voucherImageUrl: (voucherImageUrl?.isEmpty ?? true) + ? null + : voucherImageUrl, + voucherType: VoucherType.fromApi(voucherType), + status: VoucherRedemptionStatus.fromApi(status), + faceValue: faceValue ?? 0, + pointCost: pointCost ?? 0, + code: (code?.isEmpty ?? true) ? null : code, + codeExpiresAt: _parseDateTime(codeExpiresAt), + completedAt: _parseDateTime(completedAt), + createdAt: _parseDateTime(createdAt), + ); + + VoucherRedeemResult toRedeemResult() => VoucherRedeemResult( + redemption: toDomain(), + pointBalance: pointBalance ?? 0, + replayed: replayed ?? false, + ); +} + +class VoucherRedemptionPageDto { + final List items; + final WalletPaginationDto pagination; + + const VoucherRedemptionPageDto({ + required this.items, + required this.pagination, + }); + + VoucherRedemptionPage toDomain() => VoucherRedemptionPage( + items: items.map((e) => e.toDomain()).toList(), + page: pagination.page ?? 1, + totalPages: pagination.totalPages ?? 1, + ); +} diff --git a/lib/infrastructure/wallet/repositories/voucher_repository.dart b/lib/infrastructure/wallet/repositories/voucher_repository.dart new file mode 100644 index 0000000..7dd9f00 --- /dev/null +++ b/lib/infrastructure/wallet/repositories/voucher_repository.dart @@ -0,0 +1,64 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:data_channel/data_channel.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/wallet/wallet.dart'; +import '../datasources/voucher_remote_data_provider.dart'; + +@Injectable(as: IVoucherRepository) +class VoucherRepository implements IVoucherRepository { + final VoucherRemoteDataProvider _remoteDataProvider; + + final String _logName = 'VoucherRepository'; + + VoucherRepository(this._remoteDataProvider); + + @override + Future>> getVouchers() => _run( + 'getVouchers', + _remoteDataProvider.fetchVouchers, + (dtos) => dtos.map((e) => e.toDomain()).toList(), + ); + + @override + Future> redeem({ + required String voucherId, + required String pin, + required String idempotencyKey, + }) => _run( + 'redeem', + () => _remoteDataProvider.redeem( + voucherId: voucherId, + pin: pin, + idempotencyKey: idempotencyKey, + ), + (dto) => dto.toRedeemResult(), + ); + + @override + Future> getRedemptions({ + required int page, + int limit = 20, + }) => _run( + 'getRedemptions', + () => _remoteDataProvider.fetchRedemptions(page: page, limit: limit), + (dto) => dto.toDomain(), + ); + + Future> _run( + String name, + Future> Function() call, + R Function(D dto) toDomain, + ) async { + try { + final result = await call(); + if (result.hasError) return left(result.error!); + return right(toDomain(result.data as D)); + } catch (e, s) { + log(name, name: _logName, error: e, stackTrace: s); + return left(const WalletFailure.unexpectedError()); + } + } +} diff --git a/lib/infrastructure/wallet/repositories/wallet_repository.dart b/lib/infrastructure/wallet/repositories/wallet_repository.dart index 58c2ae1..5b18b13 100644 --- a/lib/infrastructure/wallet/repositories/wallet_repository.dart +++ b/lib/infrastructure/wallet/repositories/wallet_repository.dart @@ -50,15 +50,6 @@ class WalletRepository implements IWalletRepository { (dto) => dto.toDomain(), ); - @override - Future> createPaymentCode({ - required String pin, - }) => _run( - 'createPaymentCode', - () => _remoteDataProvider.createPaymentCode(pin: pin), - (dto) => dto.toDomain(), - ); - @override Future> previewExchange({ required int coins, diff --git a/lib/infrastructure/wallet/wallet_dtos.dart b/lib/infrastructure/wallet/wallet_dtos.dart index 148a541..2012ffe 100644 --- a/lib/infrastructure/wallet/wallet_dtos.dart +++ b/lib/infrastructure/wallet/wallet_dtos.dart @@ -8,12 +8,12 @@ part 'wallet_dtos.g.dart'; part 'dto/wallet_summary_dto.dart'; part 'dto/wallet_transaction_dto.dart'; part 'dto/wallet_expiring_dto.dart'; -part 'dto/payment_code_dto.dart'; part 'dto/exchange_dto.dart'; part 'dto/transfer_dto.dart'; part 'dto/pin_dto.dart'; part 'dto/outlet_dto.dart'; part 'dto/customer_order_dto.dart'; +part 'dto/voucher_dto.dart'; DateTime? _parseDateTime(String? value) => value == null ? null : DateTime.tryParse(value); diff --git a/lib/infrastructure/wallet/wallet_dtos.freezed.dart b/lib/infrastructure/wallet/wallet_dtos.freezed.dart index 8940fe3..1edac60 100644 --- a/lib/infrastructure/wallet/wallet_dtos.freezed.dart +++ b/lib/infrastructure/wallet/wallet_dtos.freezed.dart @@ -1786,282 +1786,6 @@ as List?, } -/// @nodoc -mixin _$PaymentCodeDto { - -@JsonKey(name: 'code') String? get code;@JsonKey(name: 'qr_payload') String? get qrPayload;@JsonKey(name: 'expires_at') String? get expiresAt; -/// Create a copy of PaymentCodeDto -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$PaymentCodeDtoCopyWith get copyWith => _$PaymentCodeDtoCopyWithImpl(this as PaymentCodeDto, _$identity); - - /// Serializes this PaymentCodeDto to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - final _this = this as PaymentCodeDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is PaymentCodeDto&&(identical(other.code, _this.code) || other.code == _this.code)&&(identical(other.qrPayload, _this.qrPayload) || other.qrPayload == _this.qrPayload)&&(identical(other.expiresAt, _this.expiresAt) || other.expiresAt == _this.expiresAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - final _this = this as PaymentCodeDto; - return Object.hash(runtimeType,_this.code,_this.qrPayload,_this.expiresAt); -} - -@override -String toString() { - final _this = this as PaymentCodeDto; - return 'PaymentCodeDto(code: ${_this.code}, qrPayload: ${_this.qrPayload}, expiresAt: ${_this.expiresAt})'; -} - - -} - -/// @nodoc -abstract mixin class $PaymentCodeDtoCopyWith<$Res> { - factory $PaymentCodeDtoCopyWith(PaymentCodeDto value, $Res Function(PaymentCodeDto) _then) = _$PaymentCodeDtoCopyWithImpl; -@useResult -$Res call({ -@JsonKey(name: 'code') String? code,@JsonKey(name: 'qr_payload') String? qrPayload,@JsonKey(name: 'expires_at') String? expiresAt -}); - - - - -} -/// @nodoc -class _$PaymentCodeDtoCopyWithImpl<$Res> - implements $PaymentCodeDtoCopyWith<$Res> { - _$PaymentCodeDtoCopyWithImpl(this._self, this._then); - - final PaymentCodeDto _self; - final $Res Function(PaymentCodeDto) _then; - -/// Create a copy of PaymentCodeDto -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? code = freezed,Object? qrPayload = freezed,Object? expiresAt = freezed,}) { - return _then(PaymentCodeDto( -code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable -as String?,qrPayload: freezed == qrPayload ? _self.qrPayload : qrPayload // ignore: cast_nullable_to_non_nullable -as String?,expiresAt: freezed == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [PaymentCodeDto]. -extension PaymentCodeDtoPatterns on PaymentCodeDto { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _PaymentCodeDto value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _PaymentCodeDto() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _PaymentCodeDto value) $default,){ -final _that = this; -switch (_that) { -case _PaymentCodeDto(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PaymentCodeDto value)? $default,){ -final _that = this; -switch (_that) { -case _PaymentCodeDto() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'code') String? code, @JsonKey(name: 'qr_payload') String? qrPayload, @JsonKey(name: 'expires_at') String? expiresAt)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _PaymentCodeDto() when $default != null: -return $default(_that.code,_that.qrPayload,_that.expiresAt);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'code') String? code, @JsonKey(name: 'qr_payload') String? qrPayload, @JsonKey(name: 'expires_at') String? expiresAt) $default,) {final _that = this; -switch (_that) { -case _PaymentCodeDto(): -return $default(_that.code,_that.qrPayload,_that.expiresAt);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'code') String? code, @JsonKey(name: 'qr_payload') String? qrPayload, @JsonKey(name: 'expires_at') String? expiresAt)? $default,) {final _that = this; -switch (_that) { -case _PaymentCodeDto() when $default != null: -return $default(_that.code,_that.qrPayload,_that.expiresAt);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _PaymentCodeDto extends PaymentCodeDto { - const _PaymentCodeDto({@JsonKey(name: 'code') this.code, @JsonKey(name: 'qr_payload') this.qrPayload, @JsonKey(name: 'expires_at') this.expiresAt}): super._(); - factory _PaymentCodeDto.fromJson(Map json) => _$PaymentCodeDtoFromJson(json); - -@override@JsonKey(name: 'code') final String? code; -@override@JsonKey(name: 'qr_payload') final String? qrPayload; -@override@JsonKey(name: 'expires_at') final String? expiresAt; - -/// Create a copy of PaymentCodeDto -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$PaymentCodeDtoCopyWith<_PaymentCodeDto> get copyWith => __$PaymentCodeDtoCopyWithImpl<_PaymentCodeDto>(this, _$identity); - -@override -Map toJson() { - return _$PaymentCodeDtoToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PaymentCodeDto&&(identical(other.code, code) || other.code == code)&&(identical(other.qrPayload, qrPayload) || other.qrPayload == qrPayload)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode { - return Object.hash(runtimeType,code,qrPayload,expiresAt); -} - -@override -String toString() { - return 'PaymentCodeDto(code: $code, qrPayload: $qrPayload, expiresAt: $expiresAt)'; -} - - -} - -/// @nodoc -abstract mixin class _$PaymentCodeDtoCopyWith<$Res> implements $PaymentCodeDtoCopyWith<$Res> { - factory _$PaymentCodeDtoCopyWith(_PaymentCodeDto value, $Res Function(_PaymentCodeDto) _then) = __$PaymentCodeDtoCopyWithImpl; -@override @useResult -$Res call({ -@JsonKey(name: 'code') String? code,@JsonKey(name: 'qr_payload') String? qrPayload,@JsonKey(name: 'expires_at') String? expiresAt -}); - - - - -} -/// @nodoc -class __$PaymentCodeDtoCopyWithImpl<$Res> - implements _$PaymentCodeDtoCopyWith<$Res> { - __$PaymentCodeDtoCopyWithImpl(this._self, this._then); - - final _PaymentCodeDto _self; - final $Res Function(_PaymentCodeDto) _then; - -/// Create a copy of PaymentCodeDto -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? code = freezed,Object? qrPayload = freezed,Object? expiresAt = freezed,}) { - return _then(_PaymentCodeDto( -code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable -as String?,qrPayload: freezed == qrPayload ? _self.qrPayload : qrPayload // ignore: cast_nullable_to_non_nullable -as String?,expiresAt: freezed == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - - /// @nodoc mixin _$ExchangePreviewDto { @@ -4061,7 +3785,7 @@ as String?, /// @nodoc mixin _$OutletDto { -@JsonKey(name: 'id') String? get id;@JsonKey(name: 'name') String? get name;@JsonKey(name: 'address') String? get address;@JsonKey(name: 'accepts_point_payment') bool? get acceptsPointPayment;@JsonKey(name: 'earns_points') bool? get earnsPoints;@JsonKey(name: 'earns_coins') bool? get earnsCoins; +@JsonKey(name: 'id') String? get id;@JsonKey(name: 'name') String? get name;@JsonKey(name: 'address') String? get address;@JsonKey(name: 'earns_points') bool? get earnsPoints;@JsonKey(name: 'earns_coins') bool? get earnsCoins; /// Create a copy of OutletDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -4075,20 +3799,20 @@ $OutletDtoCopyWith get copyWith => _$OutletDtoCopyWithImpl @override bool operator ==(Object other) { final _this = this as OutletDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is OutletDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.address, _this.address) || other.address == _this.address)&&(identical(other.acceptsPointPayment, _this.acceptsPointPayment) || other.acceptsPointPayment == _this.acceptsPointPayment)&&(identical(other.earnsPoints, _this.earnsPoints) || other.earnsPoints == _this.earnsPoints)&&(identical(other.earnsCoins, _this.earnsCoins) || other.earnsCoins == _this.earnsCoins)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is OutletDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.address, _this.address) || other.address == _this.address)&&(identical(other.earnsPoints, _this.earnsPoints) || other.earnsPoints == _this.earnsPoints)&&(identical(other.earnsCoins, _this.earnsCoins) || other.earnsCoins == _this.earnsCoins)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode { final _this = this as OutletDto; - return Object.hash(runtimeType,_this.id,_this.name,_this.address,_this.acceptsPointPayment,_this.earnsPoints,_this.earnsCoins); + return Object.hash(runtimeType,_this.id,_this.name,_this.address,_this.earnsPoints,_this.earnsCoins); } @override String toString() { final _this = this as OutletDto; - return 'OutletDto(id: ${_this.id}, name: ${_this.name}, address: ${_this.address}, acceptsPointPayment: ${_this.acceptsPointPayment}, earnsPoints: ${_this.earnsPoints}, earnsCoins: ${_this.earnsCoins})'; + return 'OutletDto(id: ${_this.id}, name: ${_this.name}, address: ${_this.address}, earnsPoints: ${_this.earnsPoints}, earnsCoins: ${_this.earnsCoins})'; } @@ -4099,7 +3823,7 @@ abstract mixin class $OutletDtoCopyWith<$Res> { factory $OutletDtoCopyWith(OutletDto value, $Res Function(OutletDto) _then) = _$OutletDtoCopyWithImpl; @useResult $Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'address') String? address,@JsonKey(name: 'accepts_point_payment') bool? acceptsPointPayment,@JsonKey(name: 'earns_points') bool? earnsPoints,@JsonKey(name: 'earns_coins') bool? earnsCoins +@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'address') String? address,@JsonKey(name: 'earns_points') bool? earnsPoints,@JsonKey(name: 'earns_coins') bool? earnsCoins }); @@ -4116,13 +3840,12 @@ class _$OutletDtoCopyWithImpl<$Res> /// Create a copy of OutletDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = freezed,Object? address = freezed,Object? acceptsPointPayment = freezed,Object? earnsPoints = freezed,Object? earnsCoins = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = freezed,Object? address = freezed,Object? earnsPoints = freezed,Object? earnsCoins = freezed,}) { return _then(OutletDto( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable -as String?,acceptsPointPayment: freezed == acceptsPointPayment ? _self.acceptsPointPayment : acceptsPointPayment // ignore: cast_nullable_to_non_nullable -as bool?,earnsPoints: freezed == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable +as String?,earnsPoints: freezed == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable as bool?,earnsCoins: freezed == earnsCoins ? _self.earnsCoins : earnsCoins // ignore: cast_nullable_to_non_nullable as bool?, )); @@ -4209,10 +3932,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'address') String? address, @JsonKey(name: 'accepts_point_payment') bool? acceptsPointPayment, @JsonKey(name: 'earns_points') bool? earnsPoints, @JsonKey(name: 'earns_coins') bool? earnsCoins)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'address') String? address, @JsonKey(name: 'earns_points') bool? earnsPoints, @JsonKey(name: 'earns_coins') bool? earnsCoins)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _OutletDto() when $default != null: -return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_that.earnsPoints,_that.earnsCoins);case _: +return $default(_that.id,_that.name,_that.address,_that.earnsPoints,_that.earnsCoins);case _: return orElse(); } @@ -4230,10 +3953,10 @@ return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_tha /// } /// ``` -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'address') String? address, @JsonKey(name: 'accepts_point_payment') bool? acceptsPointPayment, @JsonKey(name: 'earns_points') bool? earnsPoints, @JsonKey(name: 'earns_coins') bool? earnsCoins) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'address') String? address, @JsonKey(name: 'earns_points') bool? earnsPoints, @JsonKey(name: 'earns_coins') bool? earnsCoins) $default,) {final _that = this; switch (_that) { case _OutletDto(): -return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_that.earnsPoints,_that.earnsCoins);case _: +return $default(_that.id,_that.name,_that.address,_that.earnsPoints,_that.earnsCoins);case _: throw StateError('Unexpected subclass'); } @@ -4250,10 +3973,10 @@ return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_tha /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'address') String? address, @JsonKey(name: 'accepts_point_payment') bool? acceptsPointPayment, @JsonKey(name: 'earns_points') bool? earnsPoints, @JsonKey(name: 'earns_coins') bool? earnsCoins)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'address') String? address, @JsonKey(name: 'earns_points') bool? earnsPoints, @JsonKey(name: 'earns_coins') bool? earnsCoins)? $default,) {final _that = this; switch (_that) { case _OutletDto() when $default != null: -return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_that.earnsPoints,_that.earnsCoins);case _: +return $default(_that.id,_that.name,_that.address,_that.earnsPoints,_that.earnsCoins);case _: return null; } @@ -4265,13 +3988,12 @@ return $default(_that.id,_that.name,_that.address,_that.acceptsPointPayment,_tha @JsonSerializable() class _OutletDto extends OutletDto { - const _OutletDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'name') this.name, @JsonKey(name: 'address') this.address, @JsonKey(name: 'accepts_point_payment') this.acceptsPointPayment, @JsonKey(name: 'earns_points') this.earnsPoints, @JsonKey(name: 'earns_coins') this.earnsCoins}): super._(); + const _OutletDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'name') this.name, @JsonKey(name: 'address') this.address, @JsonKey(name: 'earns_points') this.earnsPoints, @JsonKey(name: 'earns_coins') this.earnsCoins}): super._(); factory _OutletDto.fromJson(Map json) => _$OutletDtoFromJson(json); @override@JsonKey(name: 'id') final String? id; @override@JsonKey(name: 'name') final String? name; @override@JsonKey(name: 'address') final String? address; -@override@JsonKey(name: 'accepts_point_payment') final bool? acceptsPointPayment; @override@JsonKey(name: 'earns_points') final bool? earnsPoints; @override@JsonKey(name: 'earns_coins') final bool? earnsCoins; @@ -4288,18 +4010,18 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _OutletDto&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.address, address) || other.address == address)&&(identical(other.acceptsPointPayment, acceptsPointPayment) || other.acceptsPointPayment == acceptsPointPayment)&&(identical(other.earnsPoints, earnsPoints) || other.earnsPoints == earnsPoints)&&(identical(other.earnsCoins, earnsCoins) || other.earnsCoins == earnsCoins)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _OutletDto&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.address, address) || other.address == address)&&(identical(other.earnsPoints, earnsPoints) || other.earnsPoints == earnsPoints)&&(identical(other.earnsCoins, earnsCoins) || other.earnsCoins == earnsCoins)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode { - return Object.hash(runtimeType,id,name,address,acceptsPointPayment,earnsPoints,earnsCoins); + return Object.hash(runtimeType,id,name,address,earnsPoints,earnsCoins); } @override String toString() { - return 'OutletDto(id: $id, name: $name, address: $address, acceptsPointPayment: $acceptsPointPayment, earnsPoints: $earnsPoints, earnsCoins: $earnsCoins)'; + return 'OutletDto(id: $id, name: $name, address: $address, earnsPoints: $earnsPoints, earnsCoins: $earnsCoins)'; } @@ -4310,7 +4032,7 @@ abstract mixin class _$OutletDtoCopyWith<$Res> implements $OutletDtoCopyWith<$Re factory _$OutletDtoCopyWith(_OutletDto value, $Res Function(_OutletDto) _then) = __$OutletDtoCopyWithImpl; @override @useResult $Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'address') String? address,@JsonKey(name: 'accepts_point_payment') bool? acceptsPointPayment,@JsonKey(name: 'earns_points') bool? earnsPoints,@JsonKey(name: 'earns_coins') bool? earnsCoins +@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'address') String? address,@JsonKey(name: 'earns_points') bool? earnsPoints,@JsonKey(name: 'earns_coins') bool? earnsCoins }); @@ -4327,13 +4049,12 @@ class __$OutletDtoCopyWithImpl<$Res> /// Create a copy of OutletDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = freezed,Object? address = freezed,Object? acceptsPointPayment = freezed,Object? earnsPoints = freezed,Object? earnsCoins = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = freezed,Object? address = freezed,Object? earnsPoints = freezed,Object? earnsCoins = freezed,}) { return _then(_OutletDto( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable -as String?,acceptsPointPayment: freezed == acceptsPointPayment ? _self.acceptsPointPayment : acceptsPointPayment // ignore: cast_nullable_to_non_nullable -as bool?,earnsPoints: freezed == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable +as String?,earnsPoints: freezed == earnsPoints ? _self.earnsPoints : earnsPoints // ignore: cast_nullable_to_non_nullable as bool?,earnsCoins: freezed == earnsCoins ? _self.earnsCoins : earnsCoins // ignore: cast_nullable_to_non_nullable as bool?, )); @@ -4983,7 +4704,7 @@ as int?, /// @nodoc mixin _$CustomerOrderPaymentDto { -@JsonKey(name: 'id') String? get id;@JsonKey(name: 'method_name') String? get methodName;@JsonKey(name: 'method_type') String? get methodType;@JsonKey(name: 'amount') int? get amount;@JsonKey(name: 'refund_amount') int? get refundAmount;@JsonKey(name: 'points_used') int? get pointsUsed; +@JsonKey(name: 'id') String? get id;@JsonKey(name: 'method_name') String? get methodName;@JsonKey(name: 'method_type') String? get methodType;@JsonKey(name: 'amount') int? get amount;@JsonKey(name: 'refund_amount') int? get refundAmount; /// Create a copy of CustomerOrderPaymentDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -4997,20 +4718,20 @@ $CustomerOrderPaymentDtoCopyWith get copyWith => _$Cust @override bool operator ==(Object other) { final _this = this as CustomerOrderPaymentDto; - return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomerOrderPaymentDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.methodName, _this.methodName) || other.methodName == _this.methodName)&&(identical(other.methodType, _this.methodType) || other.methodType == _this.methodType)&&(identical(other.amount, _this.amount) || other.amount == _this.amount)&&(identical(other.refundAmount, _this.refundAmount) || other.refundAmount == _this.refundAmount)&&(identical(other.pointsUsed, _this.pointsUsed) || other.pointsUsed == _this.pointsUsed)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomerOrderPaymentDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.methodName, _this.methodName) || other.methodName == _this.methodName)&&(identical(other.methodType, _this.methodType) || other.methodType == _this.methodType)&&(identical(other.amount, _this.amount) || other.amount == _this.amount)&&(identical(other.refundAmount, _this.refundAmount) || other.refundAmount == _this.refundAmount)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode { final _this = this as CustomerOrderPaymentDto; - return Object.hash(runtimeType,_this.id,_this.methodName,_this.methodType,_this.amount,_this.refundAmount,_this.pointsUsed); + return Object.hash(runtimeType,_this.id,_this.methodName,_this.methodType,_this.amount,_this.refundAmount); } @override String toString() { final _this = this as CustomerOrderPaymentDto; - return 'CustomerOrderPaymentDto(id: ${_this.id}, methodName: ${_this.methodName}, methodType: ${_this.methodType}, amount: ${_this.amount}, refundAmount: ${_this.refundAmount}, pointsUsed: ${_this.pointsUsed})'; + return 'CustomerOrderPaymentDto(id: ${_this.id}, methodName: ${_this.methodName}, methodType: ${_this.methodType}, amount: ${_this.amount}, refundAmount: ${_this.refundAmount})'; } @@ -5021,7 +4742,7 @@ abstract mixin class $CustomerOrderPaymentDtoCopyWith<$Res> { factory $CustomerOrderPaymentDtoCopyWith(CustomerOrderPaymentDto value, $Res Function(CustomerOrderPaymentDto) _then) = _$CustomerOrderPaymentDtoCopyWithImpl; @useResult $Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'method_name') String? methodName,@JsonKey(name: 'method_type') String? methodType,@JsonKey(name: 'amount') int? amount,@JsonKey(name: 'refund_amount') int? refundAmount,@JsonKey(name: 'points_used') int? pointsUsed +@JsonKey(name: 'id') String? id,@JsonKey(name: 'method_name') String? methodName,@JsonKey(name: 'method_type') String? methodType,@JsonKey(name: 'amount') int? amount,@JsonKey(name: 'refund_amount') int? refundAmount }); @@ -5038,14 +4759,13 @@ class _$CustomerOrderPaymentDtoCopyWithImpl<$Res> /// Create a copy of CustomerOrderPaymentDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? methodName = freezed,Object? methodType = freezed,Object? amount = freezed,Object? refundAmount = freezed,Object? pointsUsed = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? methodName = freezed,Object? methodType = freezed,Object? amount = freezed,Object? refundAmount = freezed,}) { return _then(CustomerOrderPaymentDto( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,methodName: freezed == methodName ? _self.methodName : methodName // ignore: cast_nullable_to_non_nullable as String?,methodType: freezed == methodType ? _self.methodType : methodType // ignore: cast_nullable_to_non_nullable as String?,amount: freezed == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable as int?,refundAmount: freezed == refundAmount ? _self.refundAmount : refundAmount // ignore: cast_nullable_to_non_nullable -as int?,pointsUsed: freezed == pointsUsed ? _self.pointsUsed : pointsUsed // ignore: cast_nullable_to_non_nullable as int?, )); } @@ -5131,10 +4851,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'method_name') String? methodName, @JsonKey(name: 'method_type') String? methodType, @JsonKey(name: 'amount') int? amount, @JsonKey(name: 'refund_amount') int? refundAmount, @JsonKey(name: 'points_used') int? pointsUsed)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'method_name') String? methodName, @JsonKey(name: 'method_type') String? methodType, @JsonKey(name: 'amount') int? amount, @JsonKey(name: 'refund_amount') int? refundAmount)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _CustomerOrderPaymentDto() when $default != null: -return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount,_that.pointsUsed);case _: +return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount);case _: return orElse(); } @@ -5152,10 +4872,10 @@ return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.re /// } /// ``` -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'method_name') String? methodName, @JsonKey(name: 'method_type') String? methodType, @JsonKey(name: 'amount') int? amount, @JsonKey(name: 'refund_amount') int? refundAmount, @JsonKey(name: 'points_used') int? pointsUsed) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'method_name') String? methodName, @JsonKey(name: 'method_type') String? methodType, @JsonKey(name: 'amount') int? amount, @JsonKey(name: 'refund_amount') int? refundAmount) $default,) {final _that = this; switch (_that) { case _CustomerOrderPaymentDto(): -return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount,_that.pointsUsed);case _: +return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount);case _: throw StateError('Unexpected subclass'); } @@ -5172,10 +4892,10 @@ return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.re /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'method_name') String? methodName, @JsonKey(name: 'method_type') String? methodType, @JsonKey(name: 'amount') int? amount, @JsonKey(name: 'refund_amount') int? refundAmount, @JsonKey(name: 'points_used') int? pointsUsed)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'method_name') String? methodName, @JsonKey(name: 'method_type') String? methodType, @JsonKey(name: 'amount') int? amount, @JsonKey(name: 'refund_amount') int? refundAmount)? $default,) {final _that = this; switch (_that) { case _CustomerOrderPaymentDto() when $default != null: -return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount,_that.pointsUsed);case _: +return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.refundAmount);case _: return null; } @@ -5187,7 +4907,7 @@ return $default(_that.id,_that.methodName,_that.methodType,_that.amount,_that.re @JsonSerializable() class _CustomerOrderPaymentDto extends CustomerOrderPaymentDto { - const _CustomerOrderPaymentDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'method_name') this.methodName, @JsonKey(name: 'method_type') this.methodType, @JsonKey(name: 'amount') this.amount, @JsonKey(name: 'refund_amount') this.refundAmount, @JsonKey(name: 'points_used') this.pointsUsed}): super._(); + const _CustomerOrderPaymentDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'method_name') this.methodName, @JsonKey(name: 'method_type') this.methodType, @JsonKey(name: 'amount') this.amount, @JsonKey(name: 'refund_amount') this.refundAmount}): super._(); factory _CustomerOrderPaymentDto.fromJson(Map json) => _$CustomerOrderPaymentDtoFromJson(json); @override@JsonKey(name: 'id') final String? id; @@ -5195,7 +4915,6 @@ class _CustomerOrderPaymentDto extends CustomerOrderPaymentDto { @override@JsonKey(name: 'method_type') final String? methodType; @override@JsonKey(name: 'amount') final int? amount; @override@JsonKey(name: 'refund_amount') final int? refundAmount; -@override@JsonKey(name: 'points_used') final int? pointsUsed; /// Create a copy of CustomerOrderPaymentDto /// with the given fields replaced by the non-null parameter values. @@ -5210,18 +4929,18 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomerOrderPaymentDto&&(identical(other.id, id) || other.id == id)&&(identical(other.methodName, methodName) || other.methodName == methodName)&&(identical(other.methodType, methodType) || other.methodType == methodType)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.refundAmount, refundAmount) || other.refundAmount == refundAmount)&&(identical(other.pointsUsed, pointsUsed) || other.pointsUsed == pointsUsed)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomerOrderPaymentDto&&(identical(other.id, id) || other.id == id)&&(identical(other.methodName, methodName) || other.methodName == methodName)&&(identical(other.methodType, methodType) || other.methodType == methodType)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.refundAmount, refundAmount) || other.refundAmount == refundAmount)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode { - return Object.hash(runtimeType,id,methodName,methodType,amount,refundAmount,pointsUsed); + return Object.hash(runtimeType,id,methodName,methodType,amount,refundAmount); } @override String toString() { - return 'CustomerOrderPaymentDto(id: $id, methodName: $methodName, methodType: $methodType, amount: $amount, refundAmount: $refundAmount, pointsUsed: $pointsUsed)'; + return 'CustomerOrderPaymentDto(id: $id, methodName: $methodName, methodType: $methodType, amount: $amount, refundAmount: $refundAmount)'; } @@ -5232,7 +4951,7 @@ abstract mixin class _$CustomerOrderPaymentDtoCopyWith<$Res> implements $Custome factory _$CustomerOrderPaymentDtoCopyWith(_CustomerOrderPaymentDto value, $Res Function(_CustomerOrderPaymentDto) _then) = __$CustomerOrderPaymentDtoCopyWithImpl; @override @useResult $Res call({ -@JsonKey(name: 'id') String? id,@JsonKey(name: 'method_name') String? methodName,@JsonKey(name: 'method_type') String? methodType,@JsonKey(name: 'amount') int? amount,@JsonKey(name: 'refund_amount') int? refundAmount,@JsonKey(name: 'points_used') int? pointsUsed +@JsonKey(name: 'id') String? id,@JsonKey(name: 'method_name') String? methodName,@JsonKey(name: 'method_type') String? methodType,@JsonKey(name: 'amount') int? amount,@JsonKey(name: 'refund_amount') int? refundAmount }); @@ -5249,19 +4968,624 @@ class __$CustomerOrderPaymentDtoCopyWithImpl<$Res> /// Create a copy of CustomerOrderPaymentDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? methodName = freezed,Object? methodType = freezed,Object? amount = freezed,Object? refundAmount = freezed,Object? pointsUsed = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? methodName = freezed,Object? methodType = freezed,Object? amount = freezed,Object? refundAmount = freezed,}) { return _then(_CustomerOrderPaymentDto( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,methodName: freezed == methodName ? _self.methodName : methodName // ignore: cast_nullable_to_non_nullable as String?,methodType: freezed == methodType ? _self.methodType : methodType // ignore: cast_nullable_to_non_nullable as String?,amount: freezed == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable as int?,refundAmount: freezed == refundAmount ? _self.refundAmount : refundAmount // ignore: cast_nullable_to_non_nullable -as int?,pointsUsed: freezed == pointsUsed ? _self.pointsUsed : pointsUsed // ignore: cast_nullable_to_non_nullable as int?, )); } +} + + +/// @nodoc +mixin _$VoucherDto { + +@JsonKey(name: 'id') String? get id;@JsonKey(name: 'name') String? get name;@JsonKey(name: 'description') String? get description;@JsonKey(name: 'image_url') String? get imageUrl;@JsonKey(name: 'voucher_type') String? get voucherType;@JsonKey(name: 'face_value') int? get faceValue;@JsonKey(name: 'point_cost') int? get pointCost;@JsonKey(name: 'max_per_customer') int? get maxPerCustomer;@JsonKey(name: 'valid_until') String? get validUntil;@JsonKey(name: 'available') int? get available; +/// Create a copy of VoucherDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherDtoCopyWith get copyWith => _$VoucherDtoCopyWithImpl(this as VoucherDto, _$identity); + + /// Serializes this VoucherDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + final _this = this as VoucherDto; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.description, _this.description) || other.description == _this.description)&&(identical(other.imageUrl, _this.imageUrl) || other.imageUrl == _this.imageUrl)&&(identical(other.voucherType, _this.voucherType) || other.voucherType == _this.voucherType)&&(identical(other.faceValue, _this.faceValue) || other.faceValue == _this.faceValue)&&(identical(other.pointCost, _this.pointCost) || other.pointCost == _this.pointCost)&&(identical(other.maxPerCustomer, _this.maxPerCustomer) || other.maxPerCustomer == _this.maxPerCustomer)&&(identical(other.validUntil, _this.validUntil) || other.validUntil == _this.validUntil)&&(identical(other.available, _this.available) || other.available == _this.available)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode { + final _this = this as VoucherDto; + return Object.hash(runtimeType,_this.id,_this.name,_this.description,_this.imageUrl,_this.voucherType,_this.faceValue,_this.pointCost,_this.maxPerCustomer,_this.validUntil,_this.available); +} + +@override +String toString() { + final _this = this as VoucherDto; + return 'VoucherDto(id: ${_this.id}, name: ${_this.name}, description: ${_this.description}, imageUrl: ${_this.imageUrl}, voucherType: ${_this.voucherType}, faceValue: ${_this.faceValue}, pointCost: ${_this.pointCost}, maxPerCustomer: ${_this.maxPerCustomer}, validUntil: ${_this.validUntil}, available: ${_this.available})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherDtoCopyWith<$Res> { + factory $VoucherDtoCopyWith(VoucherDto value, $Res Function(VoucherDto) _then) = _$VoucherDtoCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'description') String? description,@JsonKey(name: 'image_url') String? imageUrl,@JsonKey(name: 'voucher_type') String? voucherType,@JsonKey(name: 'face_value') int? faceValue,@JsonKey(name: 'point_cost') int? pointCost,@JsonKey(name: 'max_per_customer') int? maxPerCustomer,@JsonKey(name: 'valid_until') String? validUntil,@JsonKey(name: 'available') int? available +}); + + + + +} +/// @nodoc +class _$VoucherDtoCopyWithImpl<$Res> + implements $VoucherDtoCopyWith<$Res> { + _$VoucherDtoCopyWithImpl(this._self, this._then); + + final VoucherDto _self; + final $Res Function(VoucherDto) _then; + +/// Create a copy of VoucherDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? name = freezed,Object? description = freezed,Object? imageUrl = freezed,Object? voucherType = freezed,Object? faceValue = freezed,Object? pointCost = freezed,Object? maxPerCustomer = freezed,Object? validUntil = freezed,Object? available = freezed,}) { + return _then(VoucherDto( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable +as String?,voucherType: freezed == voucherType ? _self.voucherType : voucherType // ignore: cast_nullable_to_non_nullable +as String?,faceValue: freezed == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int?,pointCost: freezed == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int?,maxPerCustomer: freezed == maxPerCustomer ? _self.maxPerCustomer : maxPerCustomer // ignore: cast_nullable_to_non_nullable +as int?,validUntil: freezed == validUntil ? _self.validUntil : validUntil // ignore: cast_nullable_to_non_nullable +as String?,available: freezed == available ? _self.available : available // ignore: cast_nullable_to_non_nullable +as int?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [VoucherDto]. +extension VoucherDtoPatterns on VoucherDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _VoucherDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _VoucherDto value) $default,){ +final _that = this; +switch (_that) { +case _VoucherDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherDto value)? $default,){ +final _that = this; +switch (_that) { +case _VoucherDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'description') String? description, @JsonKey(name: 'image_url') String? imageUrl, @JsonKey(name: 'voucher_type') String? voucherType, @JsonKey(name: 'face_value') int? faceValue, @JsonKey(name: 'point_cost') int? pointCost, @JsonKey(name: 'max_per_customer') int? maxPerCustomer, @JsonKey(name: 'valid_until') String? validUntil, @JsonKey(name: 'available') int? available)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _VoucherDto() when $default != null: +return $default(_that.id,_that.name,_that.description,_that.imageUrl,_that.voucherType,_that.faceValue,_that.pointCost,_that.maxPerCustomer,_that.validUntil,_that.available);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'description') String? description, @JsonKey(name: 'image_url') String? imageUrl, @JsonKey(name: 'voucher_type') String? voucherType, @JsonKey(name: 'face_value') int? faceValue, @JsonKey(name: 'point_cost') int? pointCost, @JsonKey(name: 'max_per_customer') int? maxPerCustomer, @JsonKey(name: 'valid_until') String? validUntil, @JsonKey(name: 'available') int? available) $default,) {final _that = this; +switch (_that) { +case _VoucherDto(): +return $default(_that.id,_that.name,_that.description,_that.imageUrl,_that.voucherType,_that.faceValue,_that.pointCost,_that.maxPerCustomer,_that.validUntil,_that.available);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'name') String? name, @JsonKey(name: 'description') String? description, @JsonKey(name: 'image_url') String? imageUrl, @JsonKey(name: 'voucher_type') String? voucherType, @JsonKey(name: 'face_value') int? faceValue, @JsonKey(name: 'point_cost') int? pointCost, @JsonKey(name: 'max_per_customer') int? maxPerCustomer, @JsonKey(name: 'valid_until') String? validUntil, @JsonKey(name: 'available') int? available)? $default,) {final _that = this; +switch (_that) { +case _VoucherDto() when $default != null: +return $default(_that.id,_that.name,_that.description,_that.imageUrl,_that.voucherType,_that.faceValue,_that.pointCost,_that.maxPerCustomer,_that.validUntil,_that.available);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _VoucherDto extends VoucherDto { + const _VoucherDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'name') this.name, @JsonKey(name: 'description') this.description, @JsonKey(name: 'image_url') this.imageUrl, @JsonKey(name: 'voucher_type') this.voucherType, @JsonKey(name: 'face_value') this.faceValue, @JsonKey(name: 'point_cost') this.pointCost, @JsonKey(name: 'max_per_customer') this.maxPerCustomer, @JsonKey(name: 'valid_until') this.validUntil, @JsonKey(name: 'available') this.available}): super._(); + factory _VoucherDto.fromJson(Map json) => _$VoucherDtoFromJson(json); + +@override@JsonKey(name: 'id') final String? id; +@override@JsonKey(name: 'name') final String? name; +@override@JsonKey(name: 'description') final String? description; +@override@JsonKey(name: 'image_url') final String? imageUrl; +@override@JsonKey(name: 'voucher_type') final String? voucherType; +@override@JsonKey(name: 'face_value') final int? faceValue; +@override@JsonKey(name: 'point_cost') final int? pointCost; +@override@JsonKey(name: 'max_per_customer') final int? maxPerCustomer; +@override@JsonKey(name: 'valid_until') final String? validUntil; +@override@JsonKey(name: 'available') final int? available; + +/// Create a copy of VoucherDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherDtoCopyWith<_VoucherDto> get copyWith => __$VoucherDtoCopyWithImpl<_VoucherDto>(this, _$identity); + +@override +Map toJson() { + return _$VoucherDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherDto&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.voucherType, voucherType) || other.voucherType == voucherType)&&(identical(other.faceValue, faceValue) || other.faceValue == faceValue)&&(identical(other.pointCost, pointCost) || other.pointCost == pointCost)&&(identical(other.maxPerCustomer, maxPerCustomer) || other.maxPerCustomer == maxPerCustomer)&&(identical(other.validUntil, validUntil) || other.validUntil == validUntil)&&(identical(other.available, available) || other.available == available)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode { + return Object.hash(runtimeType,id,name,description,imageUrl,voucherType,faceValue,pointCost,maxPerCustomer,validUntil,available); +} + +@override +String toString() { + return 'VoucherDto(id: $id, name: $name, description: $description, imageUrl: $imageUrl, voucherType: $voucherType, faceValue: $faceValue, pointCost: $pointCost, maxPerCustomer: $maxPerCustomer, validUntil: $validUntil, available: $available)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherDtoCopyWith<$Res> implements $VoucherDtoCopyWith<$Res> { + factory _$VoucherDtoCopyWith(_VoucherDto value, $Res Function(_VoucherDto) _then) = __$VoucherDtoCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'id') String? id,@JsonKey(name: 'name') String? name,@JsonKey(name: 'description') String? description,@JsonKey(name: 'image_url') String? imageUrl,@JsonKey(name: 'voucher_type') String? voucherType,@JsonKey(name: 'face_value') int? faceValue,@JsonKey(name: 'point_cost') int? pointCost,@JsonKey(name: 'max_per_customer') int? maxPerCustomer,@JsonKey(name: 'valid_until') String? validUntil,@JsonKey(name: 'available') int? available +}); + + + + +} +/// @nodoc +class __$VoucherDtoCopyWithImpl<$Res> + implements _$VoucherDtoCopyWith<$Res> { + __$VoucherDtoCopyWithImpl(this._self, this._then); + + final _VoucherDto _self; + final $Res Function(_VoucherDto) _then; + +/// Create a copy of VoucherDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? name = freezed,Object? description = freezed,Object? imageUrl = freezed,Object? voucherType = freezed,Object? faceValue = freezed,Object? pointCost = freezed,Object? maxPerCustomer = freezed,Object? validUntil = freezed,Object? available = freezed,}) { + return _then(_VoucherDto( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable +as String?,voucherType: freezed == voucherType ? _self.voucherType : voucherType // ignore: cast_nullable_to_non_nullable +as String?,faceValue: freezed == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int?,pointCost: freezed == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int?,maxPerCustomer: freezed == maxPerCustomer ? _self.maxPerCustomer : maxPerCustomer // ignore: cast_nullable_to_non_nullable +as int?,validUntil: freezed == validUntil ? _self.validUntil : validUntil // ignore: cast_nullable_to_non_nullable +as String?,available: freezed == available ? _self.available : available // ignore: cast_nullable_to_non_nullable +as int?, + )); +} + + +} + + +/// @nodoc +mixin _$VoucherRedemptionDto { + +@JsonKey(name: 'id') String? get id;@JsonKey(name: 'voucher_id') String? get voucherId;@JsonKey(name: 'voucher_name') String? get voucherName;@JsonKey(name: 'voucher_image_url') String? get voucherImageUrl;@JsonKey(name: 'voucher_type') String? get voucherType;@JsonKey(name: 'status') String? get status;@JsonKey(name: 'face_value') int? get faceValue;@JsonKey(name: 'point_cost') int? get pointCost;@JsonKey(name: 'code') String? get code;@JsonKey(name: 'code_expires_at') String? get codeExpiresAt;@JsonKey(name: 'completed_at') String? get completedAt;@JsonKey(name: 'created_at') String? get createdAt;@JsonKey(name: 'point_balance') int? get pointBalance;@JsonKey(name: 'replayed') bool? get replayed; +/// Create a copy of VoucherRedemptionDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$VoucherRedemptionDtoCopyWith get copyWith => _$VoucherRedemptionDtoCopyWithImpl(this as VoucherRedemptionDto, _$identity); + + /// Serializes this VoucherRedemptionDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + final _this = this as VoucherRedemptionDto; + return identical(this, other) || (other.runtimeType == runtimeType&&other is VoucherRedemptionDto&&(identical(other.id, _this.id) || other.id == _this.id)&&(identical(other.voucherId, _this.voucherId) || other.voucherId == _this.voucherId)&&(identical(other.voucherName, _this.voucherName) || other.voucherName == _this.voucherName)&&(identical(other.voucherImageUrl, _this.voucherImageUrl) || other.voucherImageUrl == _this.voucherImageUrl)&&(identical(other.voucherType, _this.voucherType) || other.voucherType == _this.voucherType)&&(identical(other.status, _this.status) || other.status == _this.status)&&(identical(other.faceValue, _this.faceValue) || other.faceValue == _this.faceValue)&&(identical(other.pointCost, _this.pointCost) || other.pointCost == _this.pointCost)&&(identical(other.code, _this.code) || other.code == _this.code)&&(identical(other.codeExpiresAt, _this.codeExpiresAt) || other.codeExpiresAt == _this.codeExpiresAt)&&(identical(other.completedAt, _this.completedAt) || other.completedAt == _this.completedAt)&&(identical(other.createdAt, _this.createdAt) || other.createdAt == _this.createdAt)&&(identical(other.pointBalance, _this.pointBalance) || other.pointBalance == _this.pointBalance)&&(identical(other.replayed, _this.replayed) || other.replayed == _this.replayed)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode { + final _this = this as VoucherRedemptionDto; + return Object.hash(runtimeType,_this.id,_this.voucherId,_this.voucherName,_this.voucherImageUrl,_this.voucherType,_this.status,_this.faceValue,_this.pointCost,_this.code,_this.codeExpiresAt,_this.completedAt,_this.createdAt,_this.pointBalance,_this.replayed); +} + +@override +String toString() { + final _this = this as VoucherRedemptionDto; + return 'VoucherRedemptionDto(id: ${_this.id}, voucherId: ${_this.voucherId}, voucherName: ${_this.voucherName}, voucherImageUrl: ${_this.voucherImageUrl}, voucherType: ${_this.voucherType}, status: ${_this.status}, faceValue: ${_this.faceValue}, pointCost: ${_this.pointCost}, code: ${_this.code}, codeExpiresAt: ${_this.codeExpiresAt}, completedAt: ${_this.completedAt}, createdAt: ${_this.createdAt}, pointBalance: ${_this.pointBalance}, replayed: ${_this.replayed})'; +} + + +} + +/// @nodoc +abstract mixin class $VoucherRedemptionDtoCopyWith<$Res> { + factory $VoucherRedemptionDtoCopyWith(VoucherRedemptionDto value, $Res Function(VoucherRedemptionDto) _then) = _$VoucherRedemptionDtoCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'id') String? id,@JsonKey(name: 'voucher_id') String? voucherId,@JsonKey(name: 'voucher_name') String? voucherName,@JsonKey(name: 'voucher_image_url') String? voucherImageUrl,@JsonKey(name: 'voucher_type') String? voucherType,@JsonKey(name: 'status') String? status,@JsonKey(name: 'face_value') int? faceValue,@JsonKey(name: 'point_cost') int? pointCost,@JsonKey(name: 'code') String? code,@JsonKey(name: 'code_expires_at') String? codeExpiresAt,@JsonKey(name: 'completed_at') String? completedAt,@JsonKey(name: 'created_at') String? createdAt,@JsonKey(name: 'point_balance') int? pointBalance,@JsonKey(name: 'replayed') bool? replayed +}); + + + + +} +/// @nodoc +class _$VoucherRedemptionDtoCopyWithImpl<$Res> + implements $VoucherRedemptionDtoCopyWith<$Res> { + _$VoucherRedemptionDtoCopyWithImpl(this._self, this._then); + + final VoucherRedemptionDto _self; + final $Res Function(VoucherRedemptionDto) _then; + +/// Create a copy of VoucherRedemptionDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? voucherId = freezed,Object? voucherName = freezed,Object? voucherImageUrl = freezed,Object? voucherType = freezed,Object? status = freezed,Object? faceValue = freezed,Object? pointCost = freezed,Object? code = freezed,Object? codeExpiresAt = freezed,Object? completedAt = freezed,Object? createdAt = freezed,Object? pointBalance = freezed,Object? replayed = freezed,}) { + return _then(VoucherRedemptionDto( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,voucherId: freezed == voucherId ? _self.voucherId : voucherId // ignore: cast_nullable_to_non_nullable +as String?,voucherName: freezed == voucherName ? _self.voucherName : voucherName // ignore: cast_nullable_to_non_nullable +as String?,voucherImageUrl: freezed == voucherImageUrl ? _self.voucherImageUrl : voucherImageUrl // ignore: cast_nullable_to_non_nullable +as String?,voucherType: freezed == voucherType ? _self.voucherType : voucherType // ignore: cast_nullable_to_non_nullable +as String?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String?,faceValue: freezed == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int?,pointCost: freezed == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int?,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable +as String?,codeExpiresAt: freezed == codeExpiresAt ? _self.codeExpiresAt : codeExpiresAt // ignore: cast_nullable_to_non_nullable +as String?,completedAt: freezed == completedAt ? _self.completedAt : completedAt // ignore: cast_nullable_to_non_nullable +as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as String?,pointBalance: freezed == pointBalance ? _self.pointBalance : pointBalance // ignore: cast_nullable_to_non_nullable +as int?,replayed: freezed == replayed ? _self.replayed : replayed // ignore: cast_nullable_to_non_nullable +as bool?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [VoucherRedemptionDto]. +extension VoucherRedemptionDtoPatterns on VoucherRedemptionDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _VoucherRedemptionDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _VoucherRedemptionDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _VoucherRedemptionDto value) $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemptionDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _VoucherRedemptionDto value)? $default,){ +final _that = this; +switch (_that) { +case _VoucherRedemptionDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'voucher_id') String? voucherId, @JsonKey(name: 'voucher_name') String? voucherName, @JsonKey(name: 'voucher_image_url') String? voucherImageUrl, @JsonKey(name: 'voucher_type') String? voucherType, @JsonKey(name: 'status') String? status, @JsonKey(name: 'face_value') int? faceValue, @JsonKey(name: 'point_cost') int? pointCost, @JsonKey(name: 'code') String? code, @JsonKey(name: 'code_expires_at') String? codeExpiresAt, @JsonKey(name: 'completed_at') String? completedAt, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'point_balance') int? pointBalance, @JsonKey(name: 'replayed') bool? replayed)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _VoucherRedemptionDto() when $default != null: +return $default(_that.id,_that.voucherId,_that.voucherName,_that.voucherImageUrl,_that.voucherType,_that.status,_that.faceValue,_that.pointCost,_that.code,_that.codeExpiresAt,_that.completedAt,_that.createdAt,_that.pointBalance,_that.replayed);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'voucher_id') String? voucherId, @JsonKey(name: 'voucher_name') String? voucherName, @JsonKey(name: 'voucher_image_url') String? voucherImageUrl, @JsonKey(name: 'voucher_type') String? voucherType, @JsonKey(name: 'status') String? status, @JsonKey(name: 'face_value') int? faceValue, @JsonKey(name: 'point_cost') int? pointCost, @JsonKey(name: 'code') String? code, @JsonKey(name: 'code_expires_at') String? codeExpiresAt, @JsonKey(name: 'completed_at') String? completedAt, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'point_balance') int? pointBalance, @JsonKey(name: 'replayed') bool? replayed) $default,) {final _that = this; +switch (_that) { +case _VoucherRedemptionDto(): +return $default(_that.id,_that.voucherId,_that.voucherName,_that.voucherImageUrl,_that.voucherType,_that.status,_that.faceValue,_that.pointCost,_that.code,_that.codeExpiresAt,_that.completedAt,_that.createdAt,_that.pointBalance,_that.replayed);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'id') String? id, @JsonKey(name: 'voucher_id') String? voucherId, @JsonKey(name: 'voucher_name') String? voucherName, @JsonKey(name: 'voucher_image_url') String? voucherImageUrl, @JsonKey(name: 'voucher_type') String? voucherType, @JsonKey(name: 'status') String? status, @JsonKey(name: 'face_value') int? faceValue, @JsonKey(name: 'point_cost') int? pointCost, @JsonKey(name: 'code') String? code, @JsonKey(name: 'code_expires_at') String? codeExpiresAt, @JsonKey(name: 'completed_at') String? completedAt, @JsonKey(name: 'created_at') String? createdAt, @JsonKey(name: 'point_balance') int? pointBalance, @JsonKey(name: 'replayed') bool? replayed)? $default,) {final _that = this; +switch (_that) { +case _VoucherRedemptionDto() when $default != null: +return $default(_that.id,_that.voucherId,_that.voucherName,_that.voucherImageUrl,_that.voucherType,_that.status,_that.faceValue,_that.pointCost,_that.code,_that.codeExpiresAt,_that.completedAt,_that.createdAt,_that.pointBalance,_that.replayed);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _VoucherRedemptionDto extends VoucherRedemptionDto { + const _VoucherRedemptionDto({@JsonKey(name: 'id') this.id, @JsonKey(name: 'voucher_id') this.voucherId, @JsonKey(name: 'voucher_name') this.voucherName, @JsonKey(name: 'voucher_image_url') this.voucherImageUrl, @JsonKey(name: 'voucher_type') this.voucherType, @JsonKey(name: 'status') this.status, @JsonKey(name: 'face_value') this.faceValue, @JsonKey(name: 'point_cost') this.pointCost, @JsonKey(name: 'code') this.code, @JsonKey(name: 'code_expires_at') this.codeExpiresAt, @JsonKey(name: 'completed_at') this.completedAt, @JsonKey(name: 'created_at') this.createdAt, @JsonKey(name: 'point_balance') this.pointBalance, @JsonKey(name: 'replayed') this.replayed}): super._(); + factory _VoucherRedemptionDto.fromJson(Map json) => _$VoucherRedemptionDtoFromJson(json); + +@override@JsonKey(name: 'id') final String? id; +@override@JsonKey(name: 'voucher_id') final String? voucherId; +@override@JsonKey(name: 'voucher_name') final String? voucherName; +@override@JsonKey(name: 'voucher_image_url') final String? voucherImageUrl; +@override@JsonKey(name: 'voucher_type') final String? voucherType; +@override@JsonKey(name: 'status') final String? status; +@override@JsonKey(name: 'face_value') final int? faceValue; +@override@JsonKey(name: 'point_cost') final int? pointCost; +@override@JsonKey(name: 'code') final String? code; +@override@JsonKey(name: 'code_expires_at') final String? codeExpiresAt; +@override@JsonKey(name: 'completed_at') final String? completedAt; +@override@JsonKey(name: 'created_at') final String? createdAt; +@override@JsonKey(name: 'point_balance') final int? pointBalance; +@override@JsonKey(name: 'replayed') final bool? replayed; + +/// Create a copy of VoucherRedemptionDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$VoucherRedemptionDtoCopyWith<_VoucherRedemptionDto> get copyWith => __$VoucherRedemptionDtoCopyWithImpl<_VoucherRedemptionDto>(this, _$identity); + +@override +Map toJson() { + return _$VoucherRedemptionDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _VoucherRedemptionDto&&(identical(other.id, id) || other.id == id)&&(identical(other.voucherId, voucherId) || other.voucherId == voucherId)&&(identical(other.voucherName, voucherName) || other.voucherName == voucherName)&&(identical(other.voucherImageUrl, voucherImageUrl) || other.voucherImageUrl == voucherImageUrl)&&(identical(other.voucherType, voucherType) || other.voucherType == voucherType)&&(identical(other.status, status) || other.status == status)&&(identical(other.faceValue, faceValue) || other.faceValue == faceValue)&&(identical(other.pointCost, pointCost) || other.pointCost == pointCost)&&(identical(other.code, code) || other.code == code)&&(identical(other.codeExpiresAt, codeExpiresAt) || other.codeExpiresAt == codeExpiresAt)&&(identical(other.completedAt, completedAt) || other.completedAt == completedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.pointBalance, pointBalance) || other.pointBalance == pointBalance)&&(identical(other.replayed, replayed) || other.replayed == replayed)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode { + return Object.hash(runtimeType,id,voucherId,voucherName,voucherImageUrl,voucherType,status,faceValue,pointCost,code,codeExpiresAt,completedAt,createdAt,pointBalance,replayed); +} + +@override +String toString() { + return 'VoucherRedemptionDto(id: $id, voucherId: $voucherId, voucherName: $voucherName, voucherImageUrl: $voucherImageUrl, voucherType: $voucherType, status: $status, faceValue: $faceValue, pointCost: $pointCost, code: $code, codeExpiresAt: $codeExpiresAt, completedAt: $completedAt, createdAt: $createdAt, pointBalance: $pointBalance, replayed: $replayed)'; +} + + +} + +/// @nodoc +abstract mixin class _$VoucherRedemptionDtoCopyWith<$Res> implements $VoucherRedemptionDtoCopyWith<$Res> { + factory _$VoucherRedemptionDtoCopyWith(_VoucherRedemptionDto value, $Res Function(_VoucherRedemptionDto) _then) = __$VoucherRedemptionDtoCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'id') String? id,@JsonKey(name: 'voucher_id') String? voucherId,@JsonKey(name: 'voucher_name') String? voucherName,@JsonKey(name: 'voucher_image_url') String? voucherImageUrl,@JsonKey(name: 'voucher_type') String? voucherType,@JsonKey(name: 'status') String? status,@JsonKey(name: 'face_value') int? faceValue,@JsonKey(name: 'point_cost') int? pointCost,@JsonKey(name: 'code') String? code,@JsonKey(name: 'code_expires_at') String? codeExpiresAt,@JsonKey(name: 'completed_at') String? completedAt,@JsonKey(name: 'created_at') String? createdAt,@JsonKey(name: 'point_balance') int? pointBalance,@JsonKey(name: 'replayed') bool? replayed +}); + + + + +} +/// @nodoc +class __$VoucherRedemptionDtoCopyWithImpl<$Res> + implements _$VoucherRedemptionDtoCopyWith<$Res> { + __$VoucherRedemptionDtoCopyWithImpl(this._self, this._then); + + final _VoucherRedemptionDto _self; + final $Res Function(_VoucherRedemptionDto) _then; + +/// Create a copy of VoucherRedemptionDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? voucherId = freezed,Object? voucherName = freezed,Object? voucherImageUrl = freezed,Object? voucherType = freezed,Object? status = freezed,Object? faceValue = freezed,Object? pointCost = freezed,Object? code = freezed,Object? codeExpiresAt = freezed,Object? completedAt = freezed,Object? createdAt = freezed,Object? pointBalance = freezed,Object? replayed = freezed,}) { + return _then(_VoucherRedemptionDto( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,voucherId: freezed == voucherId ? _self.voucherId : voucherId // ignore: cast_nullable_to_non_nullable +as String?,voucherName: freezed == voucherName ? _self.voucherName : voucherName // ignore: cast_nullable_to_non_nullable +as String?,voucherImageUrl: freezed == voucherImageUrl ? _self.voucherImageUrl : voucherImageUrl // ignore: cast_nullable_to_non_nullable +as String?,voucherType: freezed == voucherType ? _self.voucherType : voucherType // ignore: cast_nullable_to_non_nullable +as String?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable +as String?,faceValue: freezed == faceValue ? _self.faceValue : faceValue // ignore: cast_nullable_to_non_nullable +as int?,pointCost: freezed == pointCost ? _self.pointCost : pointCost // ignore: cast_nullable_to_non_nullable +as int?,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable +as String?,codeExpiresAt: freezed == codeExpiresAt ? _self.codeExpiresAt : codeExpiresAt // ignore: cast_nullable_to_non_nullable +as String?,completedAt: freezed == completedAt ? _self.completedAt : completedAt // ignore: cast_nullable_to_non_nullable +as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as String?,pointBalance: freezed == pointBalance ? _self.pointBalance : pointBalance // ignore: cast_nullable_to_non_nullable +as int?,replayed: freezed == replayed ? _self.replayed : replayed // ignore: cast_nullable_to_non_nullable +as bool?, + )); +} + + } // dart format on diff --git a/lib/infrastructure/wallet/wallet_dtos.g.dart b/lib/infrastructure/wallet/wallet_dtos.g.dart index baeb8be..a61b272 100644 --- a/lib/infrastructure/wallet/wallet_dtos.g.dart +++ b/lib/infrastructure/wallet/wallet_dtos.g.dart @@ -112,20 +112,6 @@ _WalletExpiringDto _$WalletExpiringDtoFromJson(Map json) => Map _$WalletExpiringDtoToJson(_WalletExpiringDto instance) => {'point': instance.point, 'coin': instance.coin}; -_PaymentCodeDto _$PaymentCodeDtoFromJson(Map json) => - _PaymentCodeDto( - code: json['code'] as String?, - qrPayload: json['qr_payload'] as String?, - expiresAt: json['expires_at'] as String?, - ); - -Map _$PaymentCodeDtoToJson(_PaymentCodeDto instance) => - { - 'code': instance.code, - 'qr_payload': instance.qrPayload, - 'expires_at': instance.expiresAt, - }; - _ExchangePreviewDto _$ExchangePreviewDtoFromJson(Map json) => _ExchangePreviewDto( coinAmount: (json['coin_amount'] as num?)?.toInt(), @@ -253,7 +239,6 @@ _OutletDto _$OutletDtoFromJson(Map json) => _OutletDto( id: json['id'] as String?, name: json['name'] as String?, address: json['address'] as String?, - acceptsPointPayment: json['accepts_point_payment'] as bool?, earnsPoints: json['earns_points'] as bool?, earnsCoins: json['earns_coins'] as bool?, ); @@ -263,7 +248,6 @@ Map _$OutletDtoToJson(_OutletDto instance) => 'id': instance.id, 'name': instance.name, 'address': instance.address, - 'accepts_point_payment': instance.acceptsPointPayment, 'earns_points': instance.earnsPoints, 'earns_coins': instance.earnsCoins, }; @@ -358,7 +342,6 @@ _CustomerOrderPaymentDto _$CustomerOrderPaymentDtoFromJson( methodType: json['method_type'] as String?, amount: (json['amount'] as num?)?.toInt(), refundAmount: (json['refund_amount'] as num?)?.toInt(), - pointsUsed: (json['points_used'] as num?)?.toInt(), ); Map _$CustomerOrderPaymentDtoToJson( @@ -369,5 +352,69 @@ Map _$CustomerOrderPaymentDtoToJson( 'method_type': instance.methodType, 'amount': instance.amount, 'refund_amount': instance.refundAmount, - 'points_used': instance.pointsUsed, +}; + +_VoucherDto _$VoucherDtoFromJson(Map json) => _VoucherDto( + id: json['id'] as String?, + name: json['name'] as String?, + description: json['description'] as String?, + imageUrl: json['image_url'] as String?, + voucherType: json['voucher_type'] as String?, + faceValue: (json['face_value'] as num?)?.toInt(), + pointCost: (json['point_cost'] as num?)?.toInt(), + maxPerCustomer: (json['max_per_customer'] as num?)?.toInt(), + validUntil: json['valid_until'] as String?, + available: (json['available'] as num?)?.toInt(), +); + +Map _$VoucherDtoToJson(_VoucherDto instance) => + { + 'id': instance.id, + 'name': instance.name, + 'description': instance.description, + 'image_url': instance.imageUrl, + 'voucher_type': instance.voucherType, + 'face_value': instance.faceValue, + 'point_cost': instance.pointCost, + 'max_per_customer': instance.maxPerCustomer, + 'valid_until': instance.validUntil, + 'available': instance.available, + }; + +_VoucherRedemptionDto _$VoucherRedemptionDtoFromJson( + Map json, +) => _VoucherRedemptionDto( + id: json['id'] as String?, + voucherId: json['voucher_id'] as String?, + voucherName: json['voucher_name'] as String?, + voucherImageUrl: json['voucher_image_url'] as String?, + voucherType: json['voucher_type'] as String?, + status: json['status'] as String?, + faceValue: (json['face_value'] as num?)?.toInt(), + pointCost: (json['point_cost'] as num?)?.toInt(), + code: json['code'] as String?, + codeExpiresAt: json['code_expires_at'] as String?, + completedAt: json['completed_at'] as String?, + createdAt: json['created_at'] as String?, + pointBalance: (json['point_balance'] as num?)?.toInt(), + replayed: json['replayed'] as bool?, +); + +Map _$VoucherRedemptionDtoToJson( + _VoucherRedemptionDto instance, +) => { + 'id': instance.id, + 'voucher_id': instance.voucherId, + 'voucher_name': instance.voucherName, + 'voucher_image_url': instance.voucherImageUrl, + 'voucher_type': instance.voucherType, + 'status': instance.status, + 'face_value': instance.faceValue, + 'point_cost': instance.pointCost, + 'code': instance.code, + 'code_expires_at': instance.codeExpiresAt, + 'completed_at': instance.completedAt, + 'created_at': instance.createdAt, + 'point_balance': instance.pointBalance, + 'replayed': instance.replayed, }; diff --git a/lib/infrastructure/wallet/wallet_response.dart b/lib/infrastructure/wallet/wallet_response.dart index 09dee00..d835750 100644 --- a/lib/infrastructure/wallet/wallet_response.dart +++ b/lib/infrastructure/wallet/wallet_response.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; +import '../../common/api/interceptors/unauthorized_interceptor.dart'; import '../../domain/wallet/wallet.dart'; /// Hitung mundur bawaan bila server menjawab `429` tanpa header `Retry-After`. @@ -8,7 +9,7 @@ const int kDefaultOtpRetrySeconds = 60; /// Membaca envelope `{ success, data, errors }`. Mengembalikan `null` bila /// sukses, atau [WalletFailure] yang sesuai bila gagal. WalletFailure? parseWalletFailure(Response response) { - if (response.statusCode == 401) return const WalletFailure.unauthorized(); + if (isTokenRejected(response)) return const WalletFailure.unauthorized(); final body = response.data; if (body is! Map) return _failureFromStatus(response); diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 00d4a2c..7512a47 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -27,9 +27,6 @@ import 'package:enaklo/application/auth/set_password/set_password_form_bloc.dart as _i175; import 'package:enaklo/application/auth/verify_form/verify_form_bloc.dart' as _i521; -import 'package:enaklo/application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart' - as _i1013; -import 'package:enaklo/application/game/game_spin/game_spin_bloc.dart' as _i744; import 'package:enaklo/application/order/order_detail_loader/order_detail_loader_bloc.dart' as _i342; import 'package:enaklo/application/order/order_loader/order_loader_bloc.dart' @@ -40,10 +37,14 @@ import 'package:enaklo/application/pin/pin_change_form/pin_change_form_bloc.dart as _i538; import 'package:enaklo/application/pin/pin_setup_form/pin_setup_form_bloc.dart' as _i390; +import 'package:enaklo/application/voucher/voucher_loader/voucher_loader_bloc.dart' + as _i137; +import 'package:enaklo/application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.dart' + as _i304; +import 'package:enaklo/application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.dart' + as _i1045; import 'package:enaklo/application/wallet/exchange_form/exchange_form_bloc.dart' as _i691; -import 'package:enaklo/application/wallet/payment_code_form/payment_code_form_bloc.dart' - as _i847; import 'package:enaklo/application/wallet/transfer_form/transfer_form_bloc.dart' as _i277; import 'package:enaklo/application/wallet/wallet_expiring_loader/wallet_expiring_loader_bloc.dart' @@ -60,7 +61,6 @@ import 'package:enaklo/common/di/di_secure_storage.dart' as _i525; import 'package:enaklo/common/di/di_shared_preferences.dart' as _i672; import 'package:enaklo/common/network/network_client.dart' as _i109; import 'package:enaklo/domain/auth/auth.dart' as _i995; -import 'package:enaklo/domain/game/game.dart' as _i96; import 'package:enaklo/domain/wallet/wallet.dart' as _i66; import 'package:enaklo/env.dart' as _i372; import 'package:enaklo/infrastructure/auth/datasources/local_data_provider.dart' @@ -69,16 +69,14 @@ import 'package:enaklo/infrastructure/auth/datasources/remote_data_provider.dart as _i818; import 'package:enaklo/infrastructure/auth/repositories/auth_repository.dart' as _i879; -import 'package:enaklo/infrastructure/game/datasources/remote_data_provider.dart' - as _i143; -import 'package:enaklo/infrastructure/game/repositories/game_repository.dart' - as _i547; import 'package:enaklo/infrastructure/wallet/datasources/customer_remote_data_provider.dart' as _i852; import 'package:enaklo/infrastructure/wallet/datasources/device_data_provider.dart' as _i489; import 'package:enaklo/infrastructure/wallet/datasources/pin_remote_data_provider.dart' as _i507; +import 'package:enaklo/infrastructure/wallet/datasources/voucher_remote_data_provider.dart' + as _i159; import 'package:enaklo/infrastructure/wallet/datasources/wallet_remote_data_provider.dart' as _i152; import 'package:enaklo/infrastructure/wallet/repositories/device_repository.dart' @@ -89,6 +87,8 @@ import 'package:enaklo/infrastructure/wallet/repositories/outlet_repository.dart as _i369; import 'package:enaklo/infrastructure/wallet/repositories/pin_repository.dart' as _i846; +import 'package:enaklo/infrastructure/wallet/repositories/voucher_repository.dart' + as _i651; import 'package:enaklo/infrastructure/wallet/repositories/wallet_repository.dart' as _i189; import 'package:enaklo/presentation/router/app_router.dart' as _i698; @@ -139,9 +139,6 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i818.AuthRemoteDataProvider>( () => _i818.AuthRemoteDataProvider(gh<_i842.ApiClient>()), ); - gh.factory<_i143.GameRemoteDataProvider>( - () => _i143.GameRemoteDataProvider(gh<_i842.ApiClient>()), - ); gh.factory<_i852.CustomerRemoteDataProvider>( () => _i852.CustomerRemoteDataProvider(gh<_i842.ApiClient>()), ); @@ -151,6 +148,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i507.PinRemoteDataProvider>( () => _i507.PinRemoteDataProvider(gh<_i842.ApiClient>()), ); + gh.factory<_i159.VoucherRemoteDataProvider>( + () => _i159.VoucherRemoteDataProvider(gh<_i842.ApiClient>()), + ); gh.factory<_i152.WalletRemoteDataProvider>( () => _i152.WalletRemoteDataProvider(gh<_i842.ApiClient>()), ); @@ -169,9 +169,6 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i691.ExchangeFormBloc>( () => _i691.ExchangeFormBloc(gh<_i66.IWalletRepository>()), ); - gh.factory<_i847.PaymentCodeFormBloc>( - () => _i847.PaymentCodeFormBloc(gh<_i66.IWalletRepository>()), - ); gh.factory<_i277.TransferFormBloc>( () => _i277.TransferFormBloc(gh<_i66.IWalletRepository>()), ); @@ -187,6 +184,18 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i66.IOrderRepository>( () => _i453.OrderRepository(gh<_i852.CustomerRemoteDataProvider>()), ); + gh.factory<_i66.IVoucherRepository>( + () => _i651.VoucherRepository(gh<_i159.VoucherRemoteDataProvider>()), + ); + gh.factory<_i137.VoucherLoaderBloc>( + () => _i137.VoucherLoaderBloc(gh<_i66.IVoucherRepository>()), + ); + gh.factory<_i304.VoucherRedeemFormBloc>( + () => _i304.VoucherRedeemFormBloc(gh<_i66.IVoucherRepository>()), + ); + gh.factory<_i1045.VoucherRedemptionLoaderBloc>( + () => _i1045.VoucherRedemptionLoaderBloc(gh<_i66.IVoucherRepository>()), + ); gh.factory<_i995.IAuthRepository>( () => _i879.AuthRepository( gh<_i818.AuthRemoteDataProvider>(), @@ -202,9 +211,6 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i510.LoginFormBloc>( () => _i510.LoginFormBloc(gh<_i995.IAuthRepository>()), ); - gh.factory<_i96.IGameRepository>( - () => _i547.GameRepository(gh<_i143.GameRemoteDataProvider>()), - ); gh.factory<_i771.AuthBloc>( () => _i771.AuthBloc(gh<_i995.IAuthRepository>()), ); @@ -241,12 +247,6 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i554.OrderLoaderBloc>( () => _i554.OrderLoaderBloc(gh<_i66.IOrderRepository>()), ); - gh.factory<_i1013.FerrisWheelLoaderBloc>( - () => _i1013.FerrisWheelLoaderBloc(gh<_i96.IGameRepository>()), - ); - gh.factory<_i744.GameSpinBloc>( - () => _i744.GameSpinBloc(gh<_i96.IGameRepository>()), - ); return this; } } diff --git a/lib/presentation/pages/auth/login/login_page.dart b/lib/presentation/pages/auth/login/login_page.dart index 5d2aabe..b067d56 100644 --- a/lib/presentation/pages/auth/login/login_page.dart +++ b/lib/presentation/pages/auth/login/login_page.dart @@ -16,7 +16,12 @@ import 'widgets/phone_field.dart'; @RoutePage() class LoginPage extends StatelessWidget implements AutoRouteWrapper { - const LoginPage({super.key}); + const LoginPage({super.key, this.returnAfterLogin = false}); + + /// Login ulang dari tengah alur (mis. token kedaluwarsa saat main + /// EnakGame): setelah berhasil, halaman login di-pop dengan hasil `true` + /// alih-alih membuka beranda. + final bool returnAfterLogin; @override Widget build(BuildContext context) { @@ -40,6 +45,7 @@ class LoginPage extends StatelessWidget implements AutoRouteWrapper { context.router.push( PasswordRoute( phoneNumber: getNormalizePhone(state.phoneNumber), + returnAfterLogin: returnAfterLogin, ), ); } diff --git a/lib/presentation/pages/auth/password/password_page.dart b/lib/presentation/pages/auth/password/password_page.dart index 147789a..4a96981 100644 --- a/lib/presentation/pages/auth/password/password_page.dart +++ b/lib/presentation/pages/auth/password/password_page.dart @@ -13,7 +13,14 @@ import '../../../router/app_router.gr.dart'; @RoutePage() class PasswordPage extends StatelessWidget implements AutoRouteWrapper { final String phoneNumber; - const PasswordPage({super.key, required this.phoneNumber}); + + /// Lihat [LoginPage.returnAfterLogin]. + final bool returnAfterLogin; + const PasswordPage({ + super.key, + required this.phoneNumber, + this.returnAfterLogin = false, + }); @override Widget build(BuildContext context) { @@ -28,6 +35,12 @@ class PasswordPage extends StatelessWidget implements AutoRouteWrapper { AppFlushbar.showSuccess(context, data.message); Future.delayed(Duration(milliseconds: 1000), () { context.read().add(AuthEvent.fetchCurrentUser()); + if (returnAfterLogin) { + final router = context.router; + router.popUntilRouteWithName(LoginRoute.name); + router.pop(true); + return; + } context.router.replaceAll([MainRoute()]); }); }, diff --git a/lib/presentation/pages/enakgame/enakgame_page.dart b/lib/presentation/pages/enakgame/enakgame_page.dart new file mode 100644 index 0000000..11685db --- /dev/null +++ b/lib/presentation/pages/enakgame/enakgame_page.dart @@ -0,0 +1,368 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; + +import '../../../application/wallet/wallet_loader/wallet_loader_bloc.dart'; +import '../../../common/constant/app_constant.dart'; +import '../../../common/constant/local_storage_key.dart'; +import '../../../common/theme/theme.dart'; +import '../../../env.dart'; +import '../../../injection.dart'; +import '../../components/button/button.dart'; +import '../../router/app_router.gr.dart'; +import '../wallet/widgets/wallet_confirm_dialog.dart'; + +/// EnakGame di webview layar penuh: web game.enaklo.com yang menampilkan +/// daftar game, memulai session, memotong EnakCoin, dan menampilkan hadiah. +/// Aplikasi hanya memberi token lewat bridge (§8.3) dan tidak pernah +/// menaruhnya di URL. +@RoutePage() +class EnakGamePage extends StatefulWidget { + const EnakGamePage({super.key}); + + @override + State createState() => _EnakGamePageState(); +} + +class _EnakGamePageState extends State { + /// Game → aplikasi: JavaScript channel dengan pesan berupa JSON string. + static const String _hostChannel = 'EnakGameHost'; + + static const _nativeChannel = MethodChannel( + 'com.apskel.enaklocustomer/webview', + ); + + late final WebViewController _controller; + + /// Lewat notifier, bukan setState, supaya WebView tidak ikut di-rebuild + /// setiap progres berubah. + final ValueNotifier _progress = ValueNotifier(0); + bool _hasError = false; + bool _isRelogging = false; + bool _isExiting = false; + + String? get _token => + getIt().getString(LocalStorageKey.token); + + @override + void initState() { + super.initState(); + _controller = WebViewController(); + _load(); + } + + @override + void dispose() { + _progress.dispose(); + super.dispose(); + } + + Future _load() async { + try { + await _controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await _controller.setBackgroundColor(AppConstant.gameBackgroundColor); + await _controller.setNavigationDelegate( + NavigationDelegate( + onProgress: (progress) => _progress.value = progress, + onPageStarted: (_) { + if (mounted && _hasError) setState(() => _hasError = false); + }, + onWebResourceError: (error) { + // Error sub-resource (gambar, script pihak ketiga) diabaikan. + if (error.isForMainFrame == false) return; + if (mounted) setState(() => _hasError = true); + }, + onNavigationRequest: _onNavigationRequest, + ), + ); + // Bridge dipasang sebelum halaman dimuat. + await _controller.addJavaScriptChannel( + _hostChannel, + onMessageReceived: _onMessage, + ); + + final platform = _controller.platform; + if (platform is AndroidWebViewController) { + await _enableOffscreenPreRaster(platform); + } + // Swipe dari tepi kiri di iOS untuk mundur di riwayat web. + if (platform is WebKitWebViewController) { + await platform.setAllowsBackForwardNavigationGestures(true); + } + + await _controller.loadRequest(Uri.parse(AppConstant.gameUrl)); + } catch (e, s) { + log('load', name: 'EnakGamePage', error: e, stackTrace: s); + if (mounted) setState(() => _hasError = true); + } + } + + /// Daftar game di web di-scroll; tanpa pre-raster, Android WebView + /// menggambar ulang bagian yang kembali masuk layar. Lihat `MainActivity.kt`. + Future _enableOffscreenPreRaster( + AndroidWebViewController platform, + ) async { + try { + await _nativeChannel.invokeMethod('enableOffscreenPreRaster', { + 'webViewId': platform.webViewIdentifier, + }); + } on PlatformException catch (e, s) { + log('enableOffscreenPreRaster', error: e, stackTrace: s); + } + } + + void _onMessage(JavaScriptMessage message) { + final Object? decoded; + try { + decoded = jsonDecode(message.message); + } on FormatException { + return; + } + if (decoded is! Map) return; + + switch (decoded['type']) { + case 'ready': + _sendInit(); + case 'token_expired': + _relogin(); + case 'balance_changed': + final coinBalance = decoded['coin_balance']; + if (coinBalance is num) { + context.read().add( + WalletLoaderEvent.coinBalanceUpdated(coinBalance.toInt()), + ); + } + case 'close': + if (!_isRelogging) _exit(); + } + // Tipe lain diabaikan. + } + + /// Tanpa `game_id`: game dipilih customer di web, bukan di aplikasi. + void _sendInit() { + final token = _token; + if (token == null) { + _relogin(); + return; + } + _send({ + 'type': 'init', + 'api_base_url': '${getIt().baseUrl}/api/v1', + 'token': token, + }); + } + + /// Aplikasi → game: `window.enakGame.receive('')`. + Future _send(Map message) async { + // Encode dua kali: JSON pesan dijadikan string literal JavaScript. + final argument = jsonEncode(jsonEncode(message)); + try { + await _controller.runJavaScript( + 'window.enakGame && window.enakGame.receive($argument);', + ); + } catch (e) { + // Pesan berisi token: jangan ikut dicatat. + log('send ${message['type']} failed', name: 'EnakGamePage'); + } + } + + /// Belum ada refresh token: customer login ulang, lalu token baru dikirim + /// ke game. Bila batal, game ditutup. + Future _relogin() async { + if (_isRelogging || _isExiting) return; + _isRelogging = true; + + final proceed = await showWalletConfirmDialog( + context, + title: 'Sesi berakhir', + message: 'Masuk lagi untuk melanjutkan permainan.', + confirmLabel: 'Masuk', + ); + final loggedIn = + proceed && + mounted && + await context.router.push(LoginRoute(returnAfterLogin: true)) == + true; + + _isRelogging = false; + if (!mounted) return; + + final token = _token; + if (loggedIn && token != null) { + _send({'type': 'token', 'token': token}); + } else { + _exit(); + } + } + + /// Menutup game lalu memuat ulang saldo, karena EnakCoin bisa berubah + /// selama main. + void _exit() { + if (_isExiting) return; + _isExiting = true; + context.read().add(const WalletLoaderEvent.fetched()); + + // Dialog yang masih terbuka (mis. konfirmasi keluar) ikut ditutup. + final navigator = Navigator.of(context); + final route = ModalRoute.of(context); + navigator.popUntil((r) => r == route); + navigator.pop(); + } + + /// Tombol back Android mundur di riwayat web dulu. Di halaman pertama, + /// game tidak langsung ditutup: minta konfirmasi. + Future _onPopInvoked(bool didPop, Object? result) async { + if (didPop || _isExiting) return; + if (_hasError) { + _exit(); + return; + } + if (await _controller.canGoBack()) { + await _controller.goBack(); + return; + } + if (!mounted) return; + + final leave = await showWalletConfirmDialog( + context, + title: 'Keluar dari game?', + message: 'EnakCoin yang sudah dipakai untuk main tidak kembali.', + confirmLabel: 'Keluar', + ); + if (leave && mounted) _exit(); + } + + /// Skema selain http/https (tel:, mailto:, whatsapp:, intent:) diserahkan + /// ke aplikasi luar karena WebView tidak bisa membukanya. + Future _onNavigationRequest( + NavigationRequest request, + ) async { + final uri = Uri.tryParse(request.url); + if (uri == null || uri.scheme == 'http' || uri.scheme == 'https') { + return NavigationDecision.navigate; + } + + await launchUrl(uri, mode: LaunchMode.externalApplication); + return NavigationDecision.prevent; + } + + void _reload() { + setState(() => _hasError = false); + _controller.reload(); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: + (_hasError ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light) + .copyWith(statusBarColor: Colors.transparent), + child: PopScope( + canPop: false, + onPopInvokedWithResult: _onPopInvoked, + child: Scaffold( + backgroundColor: AppConstant.gameBackgroundColor, + // WebView tetap terpasang saat error, supaya "Coba lagi" tidak + // membuat ulang native view-nya. + body: Stack( + children: [ + _buildWebView(), + Positioned( + top: MediaQuery.paddingOf(context).top, + left: 0, + right: 0, + child: _buildProgress(), + ), + if (_hasError) Positioned.fill(child: _buildError()), + ], + ), + ), + ), + ); + } + + /// Android memakai Hybrid Composition: WebView digambar langsung oleh + /// Android, jauh lebih lancar untuk game yang terus beranimasi. + Widget _buildWebView() { + final platform = _controller.platform; + return WebViewWidget.fromPlatformCreationParams( + params: platform is AndroidWebViewController + ? AndroidWebViewWidgetCreationParams( + controller: platform, + displayWithHybridComposition: true, + ) + : PlatformWebViewWidgetCreationParams(controller: platform), + ); + } + + Widget _buildProgress() { + return ValueListenableBuilder( + valueListenable: _progress, + builder: (context, progress, _) => progress < 100 && !_hasError + ? LinearProgressIndicator( + value: progress / 100, + minHeight: 2, + color: AppColor.primary, + backgroundColor: AppColor.primaryWithOpacity(0.1), + ) + : const SizedBox(height: 2), + ); + } + + Widget _buildError() { + return ColoredBox( + color: AppColor.white, + child: SafeArea( + child: Stack( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.wifi_off, + size: 56, + color: AppColor.textSecondary, + ), + const SizedBox(height: 16), + Text( + 'Game tidak bisa dimuat. Periksa koneksi internet kamu.', + style: AppStyle.md, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + AppOutlineButton( + title: 'Coba lagi', + width: 160, + onPressed: _reload, + ), + ], + ), + ), + ), + Positioned( + top: 4, + left: 4, + child: IconButton( + icon: const Icon(Icons.close), + tooltip: 'Tutup', + onPressed: _exit, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/main/main_page.dart b/lib/presentation/pages/main/main_page.dart index cf04a2b..2c7e7fc 100644 --- a/lib/presentation/pages/main/main_page.dart +++ b/lib/presentation/pages/main/main_page.dart @@ -21,11 +21,11 @@ class _MainPageState extends State { context.read().add(const AuthEvent.fetchCurrentUser()); } - /// Kode bayar di kasir; PIN diminta di halamannya sendiri. - void _openPay() { + /// Beranda EnakPoint & EnakCoin: tukar, transfer, game, dan voucher. + void _openWallet() { final isAuthenticated = context.read().state.isAuthenticated; context.router.push( - isAuthenticated ? const PaymentCodeRoute() : const OnboardingRoute(), + isAuthenticated ? const WalletRoute() : const OnboardingRoute(), ); } @@ -48,11 +48,11 @@ class _MainPageState extends State { body: child, floatingActionButton: isKeyboardOpen ? null - : MainPayButton(onPressed: _openPay), - floatingActionButtonLocation: const MainPayButtonLocation(), + : MainWalletButton(onPressed: _openWallet), + floatingActionButtonLocation: const MainWalletButtonLocation(), bottomNavigationBar: MainBottomNavbar( tabsRouter: AutoTabsRouter.of(context), - onPayTap: _openPay, + onWalletTap: _openWallet, ), ); }, diff --git a/lib/presentation/pages/main/pages/home/widgets/point_card.dart b/lib/presentation/pages/main/pages/home/widgets/point_card.dart index 94e2289..ba70185 100644 --- a/lib/presentation/pages/main/pages/home/widgets/point_card.dart +++ b/lib/presentation/pages/main/pages/home/widgets/point_card.dart @@ -44,7 +44,7 @@ class HomePointCard extends StatelessWidget { Expanded( child: Text( isAuthenticated - ? 'Bayar di kasir, tukar, transfer & main game' + ? 'Tukar voucher, transfer & main game' : 'Silahkan login untuk melihat EnakPoint kamu', style: AppStyle.sm.copyWith( color: AppColor.textSecondary, diff --git a/lib/presentation/pages/main/pages/home/widgets/service_section.dart b/lib/presentation/pages/main/pages/home/widgets/service_section.dart index cac3d6c..1757456 100644 --- a/lib/presentation/pages/main/pages/home/widgets/service_section.dart +++ b/lib/presentation/pages/main/pages/home/widgets/service_section.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../../../application/auth/auth_bloc.dart'; -import '../../../../../../common/constant/app_constant.dart'; import '../../../../../../common/theme/theme.dart'; import '../../../../../components/assets/assets.gen.dart'; import '../../../../../components/toast/flushbar.dart'; @@ -38,12 +37,8 @@ class HomeServiceSection extends StatelessWidget { _HomeMenu( label: 'EnakGame', icon: Assets.icons.enakgameIcon, - route: WebViewRoute( - title: 'EnakGame', - url: AppConstant.gameUrl, - fullscreen: true, - backgroundColor: AppConstant.gameBackgroundColor, - ), + route: const EnakGameRoute(), + requiresLogin: true, ), _HomeMenu( label: 'EnakTransfer', @@ -55,6 +50,7 @@ class HomeServiceSection extends StatelessWidget { label: 'EnakReward', icon: Assets.icons.enakrewardIcon, route: const RewardRoute(), + requiresLogin: true, ), _HomeMenu( label: 'EnakPromo', diff --git a/lib/presentation/pages/main/widgets/bottom_navbar.dart b/lib/presentation/pages/main/widgets/bottom_navbar.dart index f54a262..1f88a01 100644 --- a/lib/presentation/pages/main/widgets/bottom_navbar.dart +++ b/lib/presentation/pages/main/widgets/bottom_navbar.dart @@ -5,29 +5,29 @@ import '../../../../common/theme/theme.dart'; class MainBottomNavbar extends StatelessWidget { final TabsRouter tabsRouter; - final VoidCallback onPayTap; + final VoidCallback onWalletTap; const MainBottomNavbar({ super.key, required this.tabsRouter, - required this.onPayTap, + required this.onWalletTap, }); - /// Slot tengah hanya tempat label tombol Bayar; lingkarannya - /// [MainPayButton] yang menempel di atas navbar. - static const int _payIndex = 2; + /// Slot tengah hanya tempat label tombol Poin; lingkarannya + /// [MainWalletButton] yang menempel di atas navbar. + static const int _walletIndex = 2; @override Widget build(BuildContext context) { final activeIndex = tabsRouter.activeIndex; return BottomNavigationBar( - currentIndex: activeIndex >= _payIndex ? activeIndex + 1 : activeIndex, + currentIndex: activeIndex >= _walletIndex ? activeIndex + 1 : activeIndex, onTap: (index) { - if (index == _payIndex) { - onPayTap(); + if (index == _walletIndex) { + onWalletTap(); return; } - tabsRouter.setActiveIndex(index > _payIndex ? index - 1 : index); + tabsRouter.setActiveIndex(index > _walletIndex ? index - 1 : index); }, items: const [ BottomNavigationBarItem( @@ -47,8 +47,8 @@ class MainBottomNavbar extends StatelessWidget { ), BottomNavigationBarItem( icon: SizedBox(height: 24), - label: 'Bayar', - tooltip: 'Bayar di kasir', + label: 'Poin', + tooltip: 'EnakPoint & EnakCoin', ), BottomNavigationBarItem( icon: Icon(Icons.groups), @@ -66,10 +66,10 @@ class MainBottomNavbar extends StatelessWidget { } /// Seperti [FloatingActionButtonLocation.centerDocked], tapi tombolnya lebih -/// menonjol ke atas supaya tidak menempel ke label "Bayar". -class MainPayButtonLocation extends StandardFabLocation +/// menonjol ke atas supaya tidak menempel ke label "Poin". +class MainWalletButtonLocation extends StandardFabLocation with FabCenterOffsetX, FabDockedOffsetY { - const MainPayButtonLocation(); + const MainWalletButtonLocation(); static const double _raise = 10; @@ -80,10 +80,10 @@ class MainPayButtonLocation extends StandardFabLocation ) => super.getOffsetY(scaffoldGeometry, adjustment) - _raise; } -/// Tombol bulat di tengah navbar, dipasang di [MainPayButtonLocation]. -class MainPayButton extends StatelessWidget { +/// Tombol bulat di tengah navbar, dipasang di [MainWalletButtonLocation]. +class MainWalletButton extends StatelessWidget { final VoidCallback onPressed; - const MainPayButton({super.key, required this.onPressed}); + const MainWalletButton({super.key, required this.onPressed}); @override Widget build(BuildContext context) { @@ -92,12 +92,12 @@ class MainPayButton extends StatelessWidget { // Halaman tab (mis. Community) juga punya FAB; tanpa hero supaya tag // default tidak bentrok saat pindah halaman. heroTag: null, - tooltip: 'Bayar di kasir', + tooltip: 'EnakPoint & EnakCoin', elevation: 4, backgroundColor: AppColor.primary, foregroundColor: AppColor.white, shape: const CircleBorder(), - child: const Icon(Icons.qr_code_scanner_rounded, size: 28), + child: const Icon(Icons.stars_rounded, size: 30), ); } } diff --git a/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart b/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart deleted file mode 100644 index bbb1165..0000000 --- a/lib/presentation/pages/mini_games/ferris_wheel/data/model.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; - -class WheelSection { - final Color color; - final IconData icon; - final String prize; - final int value; - - WheelSection({ - required this.color, - required this.icon, - required this.prize, - required this.value, - }); -} - -class PrizeHistory { - final String prize; - final DateTime dateTime; - final int value; - final Color color; - final IconData icon; - final String? gamePrizeId; // Added for API integration - - PrizeHistory({ - required this.prize, - required this.dateTime, - required this.value, - required this.color, - required this.icon, - this.gamePrizeId, - }); - - // CopyWith method for updating properties - PrizeHistory copyWith({ - String? prize, - DateTime? dateTime, - int? value, - Color? color, - IconData? icon, - String? gamePrizeId, - }) { - return PrizeHistory( - prize: prize ?? this.prize, - dateTime: dateTime ?? this.dateTime, - value: value ?? this.value, - color: color ?? this.color, - icon: icon ?? this.icon, - gamePrizeId: gamePrizeId ?? this.gamePrizeId, - ); - } - - // Convert to Map for storage/API calls - Map toMap() { - return { - 'prize': prize, - 'dateTime': dateTime.toIso8601String(), - 'value': value, - 'gamePrizeId': gamePrizeId, - }; - } - - // Create from Map for loading from storage/API - factory PrizeHistory.fromMap( - Map map, { - required Color color, - required IconData icon, - }) { - return PrizeHistory( - prize: map['prize'] ?? '', - dateTime: DateTime.tryParse(map['dateTime'] ?? '') ?? DateTime.now(), - value: map['value'] ?? 0, - color: color, - icon: icon, - gamePrizeId: map['gamePrizeId'], - ); - } - - // Format date for display - String get formattedDate { - return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; - } - - // Get prize category based on value - String get prizeCategory { - if (value >= 1000000) return 'Jackpot'; - if (value >= 100000) return 'Big Prize'; - if (value >= 10000) return 'Medium Prize'; - if (value >= 1000) return 'Small Prize'; - return 'Token/Spin'; - } - - // Get prize category color - Color get categoryColor { - if (value >= 1000000) return const Color(0xFFFFD700); // Gold - if (value >= 100000) return const Color(0xFFC0C0C0); // Silver - if (value >= 10000) return const Color(0xFFCD7F32); // Bronze - return color; // Default color - } -} diff --git a/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart b/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart deleted file mode 100644 index 3439011..0000000 --- a/lib/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart +++ /dev/null @@ -1,1086 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:audioplayers/audioplayers.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'dart:math' as math; - -import 'package:dartz/dartz.dart' hide State; - -import '../../../../application/auth/auth_bloc.dart'; -import '../../../../application/game/ferris_wheel_loader/ferris_wheel_loader_bloc.dart'; -import '../../../../application/game/game_spin/game_spin_bloc.dart'; -import '../../../../application/wallet/wallet_loader/wallet_loader_bloc.dart'; -import '../../../../common/extension/extension.dart'; -import '../../../../common/theme/theme.dart'; -import '../../../../common/painter/wheel_painter.dart'; -import '../../../../injection.dart'; -import '../../../../domain/game/game.dart'; -import '../../../../domain/wallet/wallet.dart'; -import '../../../components/toast/flushbar.dart'; -import '../../wallet/wallet_ui.dart'; - -// Simple models for UI state -class PrizeHistory { - final String prize; - final DateTime dateTime; - final int value; - final Color color; - final IconData icon; - final String? gamePrizeId; - - PrizeHistory({ - required this.prize, - required this.dateTime, - required this.value, - required this.color, - required this.icon, - this.gamePrizeId, - }); -} - -@RoutePage() -class FerrisWheelPage extends StatefulWidget implements AutoRouteWrapper { - const FerrisWheelPage({super.key}); - - @override - State createState() => _FerrisWheelPageState(); - - @override - Widget wrappedRoute(BuildContext context) => MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - getIt() - ..add(const FerrisWheelLoaderEvent.fetched()), - ), - BlocProvider(create: (context) => getIt()), - ], - child: this, - ); -} - -class _FerrisWheelPageState extends State - with TickerProviderStateMixin { - // Animation Controllers - late AnimationController _rotationController; - late AnimationController _glowController; - late AnimationController _pulseController; - late AnimationController _idleRotationController; - - // Animations - Animation? _spinAnimation; - Animation? _pulseAnimation; - Animation? _idleRotationAnimation; - - // Audio Players - final AudioPlayer _bgmPlayer = AudioPlayer(); - final AudioPlayer _sfxPlayer = AudioPlayer(); - - // Audio Settings - bool _isSoundEnabled = true; - bool _isMusicEnabled = true; - - // Game State - bool isSpinning = false; - String resultText = 'Belum pernah spin'; - double currentRotation = 0.0; - int currentTabIndex = 0; - - // Game Data - List prizeHistory = []; - List gamePrizes = []; - - @override - void initState() { - super.initState(); - _initializeAudio(); - _initializeAnimations(); - context.read().add(const WalletLoaderEvent.fetched()); - } - - @override - void dispose() { - _rotationController.dispose(); - _glowController.dispose(); - _pulseController.dispose(); - _idleRotationController.dispose(); - _bgmPlayer.dispose(); - _sfxPlayer.dispose(); - super.dispose(); - } - - void _initializeAudio() async { - try { - await _bgmPlayer.setSource(AssetSource('audio/carnaval_main_theme.mp3')); - await _bgmPlayer.setReleaseMode(ReleaseMode.loop); - await _bgmPlayer.setVolume(0.5); - if (_isMusicEnabled) await _bgmPlayer.resume(); - } catch (e) { - print('Error initializing audio: $e'); - } - } - - void _playSound(String soundPath, {double volume = 0.7}) async { - if (!_isSoundEnabled) return; - try { - await _sfxPlayer.stop(); - await _sfxPlayer.setSource(AssetSource(soundPath)); - await _sfxPlayer.setVolume(volume); - await _sfxPlayer.resume(); - } catch (e) { - print('Error playing sound: $e'); - } - } - - void _playButtonTap() => _playSound('audio/button_tap.mp3', volume: 0.3); - void _playTokenSound() => _playSound('audio/token_sound.mp3', volume: 0.5); - void _playWheelSpin() => _playSound('audio/wheel_spin.mp3', volume: 0.8); - void _playWinSound() => _playSound('audio/win_medium.mp3', volume: 0.8); - - void _toggleSound() { - setState(() => _isSoundEnabled = !_isSoundEnabled); - if (_isSoundEnabled) _playButtonTap(); - } - - void _toggleMusic() { - setState(() => _isMusicEnabled = !_isMusicEnabled); - _isMusicEnabled ? _bgmPlayer.resume() : _bgmPlayer.pause(); - if (_isSoundEnabled) _playButtonTap(); - } - - void _initializeAnimations() { - _rotationController = AnimationController( - duration: const Duration(seconds: 4), - vsync: this, - ); - _glowController = AnimationController( - duration: const Duration(milliseconds: 2000), - vsync: this, - ); - _pulseController = AnimationController( - duration: const Duration(milliseconds: 800), - vsync: this, - ); - _idleRotationController = AnimationController( - duration: const Duration(seconds: 10), - vsync: this, - ); - - _pulseAnimation = Tween(begin: 1.0, end: 1.1).animate( - CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), - ); - _idleRotationAnimation = Tween( - begin: 0.0, - end: 2 * math.pi, - ).animate(_idleRotationController); - - _glowController.repeat(reverse: true); - _pulseController.repeat(reverse: true); - _idleRotationController.repeat(); - } - - int get _coinBalance => - context.read().state.summary.coinBalance; - - int get _coinCost => - context.read().state.ferrisWheel.coinCost; - - Color _getPrizeColor(GamePrize prize, int index) { - final colors = [ - AppColor.primary, - AppColor.info, - AppColor.warning, - AppColor.success, - AppColor.primaryDark, - AppColor.secondary, - AppColor.error, - ]; - return colors[index % colors.length]; - } - - /// Hadiah ditentukan server; roda hanya dianimasikan ke hadiah itu. - void _spinWheel() { - if (isSpinning || gamePrizes.isEmpty) return; - - final game = context.read().state.ferrisWheel; - if (_coinBalance < _coinCost) { - AppFlushbar.showError( - context, - 'EnakCoin kamu kurang. Butuh $_coinCost EnakCoin untuk main.', - ); - return; - } - - _playTokenSound(); - setState(() { - isSpinning = true; - resultText = 'Sedang berputar...'; - }); - - context.read().add(GameSpinEvent.spun(game.id)); - } - - void _onSpinResult(Either either) { - either.fold( - (f) { - final message = f.map( - serverError: (value) => value.failure.toStringFormatted(context), - unexpectedError: (_) => 'Terjadi kesalahan, coba lagi', - dynamicErrorMessage: (value) => value.erroMessage, - ); - setState(() { - isSpinning = false; - resultText = 'Belum berhasil, silakan coba lagi'; - }); - AppFlushbar.showError(context, message); - }, - (result) { - context.read().add( - WalletLoaderEvent.coinBalanceUpdated(result.coinsRemaining), - ); - - var index = gamePrizes.indexWhere((p) => p.id == result.prizeId); - if (index < 0) { - index = gamePrizes.indexWhere((p) => p.name == result.prizeName); - } - _animateToPrize( - index < 0 ? 0 : index, - index < 0 - ? GamePrize.empty().copyWith( - id: result.prizeId, - name: result.prizeName, - ) - : gamePrizes[index], - ); - }, - ); - } - - void _animateToPrize(int selectedPrizeIndex, GamePrize wonPrize) { - _playWheelSpin(); - - _idleRotationController.stop(); - _idleRotationController.reset(); - - double sectionAngle = (2 * math.pi) / gamePrizes.length; - double currentPos = currentRotation; - - // TAMBAH offset ke tengah section (bukan garis) - double targetForSelectedSection = - -(selectedPrizeIndex * sectionAngle) - (sectionAngle / 2); - - double spins = 6 * 2 * math.pi; - double finalRotation = currentPos + spins + targetForSelectedSection; - - while (finalRotation <= currentPos + spins) { - finalRotation += 2 * math.pi; - } - - _spinAnimation = Tween(begin: currentPos, end: finalRotation) - .animate( - CurvedAnimation( - parent: _rotationController, - curve: Curves.easeOutCubic, - ), - ); - - _rotationController.reset(); - _rotationController.animateTo(1.0).then((_) { - if (!mounted) return; - _playWinSound(); - - setState(() { - currentRotation = finalRotation; - isSpinning = false; - resultText = 'Selamat! Anda mendapat ${wonPrize.name}!'; - prizeHistory.insert( - 0, - PrizeHistory( - prize: wonPrize.name, - dateTime: DateTime.now(), - value: 100, - color: _getPrizeColor(wonPrize, selectedPrizeIndex), - icon: Icons.card_giftcard, - gamePrizeId: wonPrize.id, - ), - ); - }); - - _showWinDialog(wonPrize); - _idleRotationController.repeat(); - }); - } - - void _showWinDialog(GamePrize wonPrize) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - child: Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: AppColor.primaryGradient, - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(40), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.2), - blurRadius: 15, - spreadRadius: 2, - ), - ], - ), - child: Icon( - Icons.card_giftcard, - color: AppColor.primary, - size: 40, - ), - ), - const SizedBox(height: 16), - Text( - 'Selamat!', - style: AppStyle.h5.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textWhite, - ), - ), - const SizedBox(height: 8), - Text( - 'Anda mendapatkan', - style: AppStyle.lg.copyWith(color: AppColor.textWhite), - ), - const SizedBox(height: 4), - Text( - wonPrize.name, - style: AppStyle.h5.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textWhite, - ), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () { - _playButtonTap(); - Navigator.of(context).pop(); - }, - style: ElevatedButton.styleFrom( - backgroundColor: AppColor.white, - foregroundColor: AppColor.primary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(25), - ), - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 12, - ), - ), - child: Text( - 'Terima Kasih', - style: AppStyle.md.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.primary, - ), - ), - ), - ], - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return BlocListener( - listenWhen: (p, c) => p.failureOrResultOption != c.failureOrResultOption, - listener: (context, state) => - state.failureOrResultOption.fold(() {}, _onSpinResult), - child: _buildPage(), - ); - } - - Widget _buildPage() { - return BlocBuilder( - builder: (context, state) { - if (state.isFetching) { - return Scaffold( - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: AppColor.primaryGradient, - ), - ), - child: Center( - child: SpinKitFadingCircle(color: AppColor.textWhite, size: 36), - ), - ), - ); - } - - if (gamePrizes.isEmpty && state.ferrisWheel.prizes.isNotEmpty) { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - gamePrizes = state.ferrisWheel.prizes; - }); - }); - } - - return Scaffold( - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: AppColor.primaryGradient, - ), - ), - child: SafeArea( - child: Column( - children: [ - _buildHeader(), - _buildTabSelector(), - const SizedBox(height: 20), - Expanded( - child: currentTabIndex == 0 - ? _buildMainContent() - : currentTabIndex == 1 - ? _buildPrizeListContent() - : _buildHistoryContent(), - ), - ], - ), - ), - ), - ); - }, - ); - } - - Widget _buildHeader() { - return Container( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - IconButton( - onPressed: () { - _playButtonTap(); - context.router.back(); - }, - icon: Icon(Icons.close, color: AppColor.textWhite, size: 28), - ), - Expanded( - child: Text( - 'SPIN & WIN', - style: AppStyle.h6.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textWhite, - letterSpacing: 2, - ), - ), - ), - IconButton( - onPressed: _toggleMusic, - icon: Icon( - _isMusicEnabled ? Icons.volume_up : Icons.volume_off, - color: AppColor.textWhite, - ), - ), - IconButton( - onPressed: _toggleSound, - icon: Icon( - _isSoundEnabled ? Icons.graphic_eq : Icons.volume_mute, - color: AppColor.textWhite, - ), - ), - ], - ), - ); - } - - Widget _buildTabSelector() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 20), - decoration: BoxDecoration( - color: AppColor.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(25), - ), - child: Row( - children: [ - for (int i = 0; i < 3; i++) - Expanded( - child: GestureDetector( - onTap: () { - _playButtonTap(); - setState(() => currentTabIndex = i); - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: currentTabIndex == i - ? AppColor.white - : Colors.transparent, - borderRadius: BorderRadius.circular(25), - ), - child: Text( - ['Spin Wheel', 'Daftar Hadiah', 'Riwayat'][i], - textAlign: TextAlign.center, - style: AppStyle.md.copyWith( - fontWeight: FontWeight.bold, - color: currentTabIndex == i - ? AppColor.primary - : AppColor.textWhite, - ), - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildMainContent() { - return Column( - children: [ - _buildUserInfoCard(), - Text( - resultText, - style: AppStyle.md.copyWith(color: AppColor.textWhite), - ), - const SizedBox(height: 20), - Expanded( - child: Center( - child: gamePrizes.isEmpty - ? SpinKitFadingCircle(color: AppColor.textWhite, size: 36) - : _buildWheelSection(), - ), - ), - _buildBottomBanner(), - ], - ); - } - - Widget _buildUserInfoCard() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(15), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - BlocBuilder( - builder: (context, auth) { - return Row( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: AppColor.primary, - borderRadius: BorderRadius.circular(8), - ), - child: Center( - child: Text( - 'G', - style: AppStyle.lg.copyWith( - color: AppColor.textWhite, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - auth.user.name, - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - Text( - auth.user.phoneNumber, - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - ], - ), - ], - ); - }, - ), - Row( - children: [ - const WalletCurrencyIcon(currency: WalletCurrency.coin, size: 20), - const SizedBox(width: 8), - BlocBuilder( - builder: (context, wallet) => Text( - '${wallet.summary.coinBalance.thousandFormat} EnakCoin', - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - ), - ], - ), - ], - ), - ); - } - - Widget _buildWheelSection() { - return Stack( - alignment: Alignment.center, - children: [ - // Glow Effect - AnimatedBuilder( - animation: _glowController, - builder: (context, child) => Container( - width: 340, - height: 340, - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: AppColor.white.withOpacity( - 0.3 + 0.2 * _glowController.value, - ), - blurRadius: 40, - spreadRadius: 10, - ), - ], - ), - ), - ), - // Spinning Wheel - AnimatedBuilder( - animation: isSpinning ? _rotationController : _idleRotationController, - builder: (context, child) { - double rotationAngle = isSpinning - ? (_spinAnimation?.value ?? currentRotation) - : (currentRotation + (_idleRotationAnimation?.value ?? 0.0)); - - return Transform.rotate( - angle: rotationAngle, - child: CustomPaint( - size: const Size(320, 320), - painter: WheelPainter( - gamePrizes: gamePrizes, - getPrizeColor: _getPrizeColor, - ), - ), - ); - }, - ), - // Spin Button - _buildSpinButton(), - // Pointer - Positioned( - top: 30, - child: Container( - width: 0, - height: 0, - decoration: BoxDecoration( - border: Border( - left: BorderSide(width: 15, color: Colors.transparent), - right: BorderSide(width: 15, color: Colors.transparent), - bottom: BorderSide(width: 30, color: AppColor.error), - ), - ), - ), - ), - ], - ); - } - - Widget _buildSpinButton() { - return BlocSelector( - selector: (state) => state.summary.coinBalance, - builder: (context, coinBalance) => Opacity( - opacity: coinBalance >= _coinCost ? 1 : 0.5, - child: _buildSpinButtonBody(), - ), - ); - } - - Widget _buildSpinButtonBody() { - return AnimatedBuilder( - animation: _pulseAnimation ?? const AlwaysStoppedAnimation(1.0), - builder: (context, child) => Transform.scale( - scale: _pulseAnimation?.value ?? 1.0, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [AppColor.warning, AppColor.warning], - ), - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.3), - blurRadius: 15, - offset: const Offset(0, 6), - ), - BoxShadow( - color: AppColor.warning.withOpacity(0.5), - blurRadius: 20, - spreadRadius: (_pulseAnimation?.value ?? 1.0) * 5, - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(50), - onTap: _spinWheel, - child: Center( - child: Text( - 'SPIN', - style: AppStyle.lg.copyWith( - color: AppColor.textWhite, - fontWeight: FontWeight.bold, - letterSpacing: 1, - ), - ), - ), - ), - ), - ), - ), - ); - } - - Widget _buildBottomBanner() { - return BlocSelector( - selector: (state) => state.summary.coinBalance, - builder: (context, coinBalance) { - final cost = _coinCost; - return Container( - margin: const EdgeInsets.all(20), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [AppColor.warning, AppColor.warning], - ), - borderRadius: BorderRadius.circular(15), - ), - child: Text( - coinBalance >= cost - ? 'Sekali main: $cost EnakCoin' - : 'Sekali main: $cost EnakCoin. EnakCoin kamu belum cukup.', - textAlign: TextAlign.center, - style: AppStyle.lg.copyWith( - color: AppColor.textWhite, - fontWeight: FontWeight.bold, - ), - ), - ); - }, - ); - } - - Widget _buildPrizeListContent() { - return Container( - margin: const EdgeInsets.all(20), - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(15), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Text( - 'Daftar Hadiah', - style: AppStyle.h6.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - ), - const SizedBox(height: 20), - Expanded( - child: gamePrizes.isEmpty - ? Center( - child: SpinKitFadingCircle( - color: AppColor.white.withOpacity(0.7), - size: 36, - ), - ) - : ListView.builder( - itemCount: gamePrizes.length, - itemBuilder: (context, index) { - final prize = gamePrizes[index]; - return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.05), - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - children: [ - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: _getPrizeColor(prize, index), - borderRadius: BorderRadius.circular(25), - ), - child: Icon( - Icons.card_giftcard, - color: AppColor.white, - size: 24, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - prize.name, - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - Text( - 'ID: ${prize.id}', - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - Text( - 'Game ID: ${prize.gameId}', - style: AppStyle.xs.copyWith( - color: AppColor.textSecondary, - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: _getPrizeColor( - prize, - index, - ).withOpacity(0.1), - borderRadius: BorderRadius.circular(15), - ), - child: Text( - 'Hadiah', - style: AppStyle.xs.copyWith( - color: _getPrizeColor(prize, index), - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ); - }, - ), - ), - ], - ), - ); - } - - Widget _buildHistoryContent() { - return Container( - margin: const EdgeInsets.all(20), - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(15), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Text( - 'Riwayat Hadiah', - style: AppStyle.h6.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - ), - const SizedBox(height: 20), - Expanded( - child: prizeHistory.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.history, - size: 80, - color: AppColor.white.withOpacity(0.5), - ), - const SizedBox(height: 16), - Text( - 'Belum ada riwayat spin', - style: AppStyle.lg.copyWith( - color: AppColor.white.withOpacity(0.7), - ), - ), - const SizedBox(height: 8), - Text( - 'Mulai spin untuk melihat riwayat hadiah', - style: AppStyle.md.copyWith( - color: AppColor.white.withOpacity(0.5), - ), - ), - ], - ), - ) - : ListView.builder( - itemCount: prizeHistory.length, - itemBuilder: (context, index) { - final history = prizeHistory[index]; - return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: AppColor.black.withOpacity(0.05), - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - children: [ - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: history.color, - borderRadius: BorderRadius.circular(25), - ), - child: Icon( - history.icon, - color: AppColor.white, - size: 24, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - history.prize, - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - Text( - '${history.dateTime.day}/${history.dateTime.month}/${history.dateTime.year} ${history.dateTime.hour.toString().padLeft(2, '0')}:${history.dateTime.minute.toString().padLeft(2, '0')}', - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - if (history.gamePrizeId != null) - Text( - 'ID: ${history.gamePrizeId}', - style: AppStyle.xs.copyWith( - color: AppColor.textSecondary - .withOpacity(0.7), - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: AppColor.success.withOpacity(0.1), - borderRadius: BorderRadius.circular(15), - ), - child: Text( - 'Menang', - style: AppStyle.sm.copyWith( - color: AppColor.success, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ); - }, - ), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/pages/onboarding/onboarding_page.dart b/lib/presentation/pages/onboarding/onboarding_page.dart index 6fb6749..857cc49 100644 --- a/lib/presentation/pages/onboarding/onboarding_page.dart +++ b/lib/presentation/pages/onboarding/onboarding_page.dart @@ -173,7 +173,7 @@ class _OnboardingPageState extends State { const Spacer(), AppElevatedButton( - onPressed: () => context.router.push(const LoginRoute()), + onPressed: () => context.router.push(LoginRoute()), title: 'Masuk', ), diff --git a/lib/presentation/pages/order/order_detail/order_detail_page.dart b/lib/presentation/pages/order/order_detail/order_detail_page.dart index 1488647..e757a9f 100644 --- a/lib/presentation/pages/order/order_detail/order_detail_page.dart +++ b/lib/presentation/pages/order/order_detail/order_detail_page.dart @@ -437,26 +437,17 @@ class _PaymentRow extends StatelessWidget { @override Widget build(BuildContext context) { - // Pembayaran EnakPoint: "EnakPoint 12.500 (Rp 12.500)". - final title = payment.isPoint && payment.pointsUsed != null - ? '${WalletCurrency.point.label} ${payment.pointsUsed!.thousandFormat}' - : payment.methodName; - return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (payment.isPoint) ...[ - const WalletCurrencyIcon(currency: WalletCurrency.point, size: 20), - const SizedBox(width: 8), - ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - title, + payment.methodName, style: AppStyle.sm.copyWith(fontWeight: FontWeight.w600), ), if (payment.refundAmount > 0) @@ -468,9 +459,7 @@ class _PaymentRow extends StatelessWidget { ), ), Text( - payment.isPoint - ? '(${payment.amount.currencyFormatRp})' - : payment.amount.currencyFormatRp, + payment.amount.currencyFormatRp, style: AppStyle.sm.copyWith(fontWeight: FontWeight.w600), ), ], diff --git a/lib/presentation/pages/outlet/widgets/outlet_tile.dart b/lib/presentation/pages/outlet/widgets/outlet_tile.dart index ff9049f..5b1f09b 100644 --- a/lib/presentation/pages/outlet/widgets/outlet_tile.dart +++ b/lib/presentation/pages/outlet/widgets/outlet_tile.dart @@ -53,24 +53,12 @@ class OutletTile extends StatelessWidget { style: AppStyle.xs.copyWith(color: AppColor.textSecondary), ), ], - if (outlet.acceptsPointPayment || - outlet.earnsPoints || - outlet.earnsCoins) ...[ + if (outlet.earnsPoints || outlet.earnsCoins) ...[ const SizedBox(height: 8), Wrap( spacing: 6, runSpacing: 6, children: [ - if (outlet.acceptsPointPayment) - const _Chip( - label: 'Bisa bayar pakai EnakPoint', - icon: Icon( - Icons.check_circle_rounded, - size: 14, - color: AppColor.primary, - ), - highlighted: true, - ), if (outlet.earnsPoints) const _Chip( label: 'Dapat EnakPoint', @@ -100,24 +88,17 @@ class OutletTile extends StatelessWidget { } class _Chip extends StatelessWidget { - const _Chip({ - required this.label, - required this.icon, - this.highlighted = false, - }); + const _Chip({required this.label, required this.icon}); final String label; final Widget icon; - final bool highlighted; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: highlighted - ? AppColor.primaryWithOpacity(0.08) - : AppColor.background, + color: AppColor.background, borderRadius: BorderRadius.circular(999), ), child: Row( @@ -129,7 +110,7 @@ class _Chip extends StatelessWidget { label, style: AppStyle.xs.copyWith( fontSize: 10, - color: highlighted ? AppColor.primary : AppColor.textPrimary, + color: AppColor.textPrimary, fontWeight: FontWeight.w700, ), ), diff --git a/lib/presentation/pages/pin/pin_settings_page.dart b/lib/presentation/pages/pin/pin_settings_page.dart index baa29d6..f6c054c 100644 --- a/lib/presentation/pages/pin/pin_settings_page.dart +++ b/lib/presentation/pages/pin/pin_settings_page.dart @@ -79,9 +79,9 @@ class _PinSettingsPageState extends State { borderRadius: BorderRadius.circular(AppValue.borderRadius), ), child: Text( - 'PIN 6 digit dipakai untuk membuat kode bayar, tukar ' - 'EnakCoin, dan transfer. PIN berbeda dengan password ' - 'login dan jangan diberitahukan kepada siapa pun.', + 'PIN 6 digit dipakai untuk tukar EnakCoin, transfer, dan ' + 'tukar voucher. PIN berbeda dengan password login dan ' + 'jangan diberitahukan kepada siapa pun.', style: AppStyle.md.copyWith(color: AppColor.textSecondary), ), ), diff --git a/lib/presentation/pages/pin/pin_setup_page.dart b/lib/presentation/pages/pin/pin_setup_page.dart index ae77280..36ddd9a 100644 --- a/lib/presentation/pages/pin/pin_setup_page.dart +++ b/lib/presentation/pages/pin/pin_setup_page.dart @@ -189,8 +189,8 @@ class _PinSetupPageState extends State { _Step.otp => 'Kami mengirim kode 6 digit ke WhatsApp nomor kamu yang terdaftar.', _Step.pin => - 'PIN 6 digit dipakai untuk membuat kode bayar, tukar EnakCoin, dan ' - 'transfer. PIN berbeda dengan password login.', + 'PIN 6 digit dipakai untuk tukar EnakCoin, transfer, dan tukar ' + 'voucher. PIN berbeda dengan password login.', _Step.confirm => 'Masukkan kembali PIN yang sama.', _Step.done => '', }; @@ -318,9 +318,9 @@ class _PinSetupPageState extends State { Text( _isReset ? 'Demi keamanan, transfer keluar ditahan selama 24 jam. ' - 'Kode bayar dan tukar EnakCoin tetap bisa dipakai.' - : 'Gunakan PIN ini untuk membuat kode bayar, tukar EnakCoin, ' - 'dan transfer. Jangan beritahu PIN kepada siapa pun.', + 'Tukar EnakCoin dan tukar voucher tetap bisa dipakai.' + : 'Gunakan PIN ini untuk tukar EnakCoin, transfer, dan tukar ' + 'voucher. Jangan beritahu PIN kepada siapa pun.', style: AppStyle.md.copyWith(color: AppColor.textSecondary), textAlign: TextAlign.center, ), diff --git a/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart b/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart index 52c8f2e..d07e382 100644 --- a/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart +++ b/lib/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart @@ -1,6 +1,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../application/auth/auth_bloc.dart'; import '../../../../../common/theme/theme.dart'; import '../../../../../sample/promo_sample_data.dart'; import '../../../../components/button/button.dart'; @@ -98,9 +100,14 @@ class PromoDetailPage extends StatelessWidget { child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: AppElevatedButton( - title: isReward ? 'Lihat Reward Saya' : 'Cari Outlet', + title: isReward ? 'Buka EnakReward' : 'Cari Outlet', + // Voucher dan outlet memakai API customer: tamu masuk dulu. onPressed: () => context.router.push( - isReward ? const RewardRoute() : const OutletRoute(), + !context.read().state.isAuthenticated + ? const OnboardingRoute() + : isReward + ? const RewardRoute() + : const OutletRoute(), ), ), ), diff --git a/lib/presentation/pages/reward/reward_detail_page.dart b/lib/presentation/pages/reward/reward_detail_page.dart new file mode 100644 index 0000000..df6a2dd --- /dev/null +++ b/lib/presentation/pages/reward/reward_detail_page.dart @@ -0,0 +1,410 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../application/voucher/voucher_redeem_form/voucher_redeem_form_bloc.dart'; +import '../../../application/wallet/wallet_loader/wallet_loader_bloc.dart'; +import '../../../common/extension/extension.dart'; +import '../../../common/theme/theme.dart'; +import '../../../domain/wallet/wallet.dart'; +import '../../../injection.dart'; +import '../../components/button/button.dart'; +import '../../components/toast/flushbar.dart'; +import '../pin/pin_guard.dart'; +import '../pin/widgets/pin_sheet.dart'; +import '../wallet/wallet_ui.dart'; +import '../wallet/widgets/wallet_confirm_dialog.dart'; +import 'reward_ui.dart'; + +/// Detail voucher di katalog dan tukar EnakPoint ke voucher (§9.2). Pop +/// dengan `true` bila voucher berhasil ditukar. +@RoutePage() +class RewardDetailPage extends StatefulWidget implements AutoRouteWrapper { + const RewardDetailPage({super.key, required this.voucher}); + + final Voucher voucher; + + @override + State createState() => _RewardDetailPageState(); + + @override + Widget wrappedRoute(BuildContext context) => + BlocProvider(create: (_) => getIt(), child: this); +} + +class _RewardDetailPageState extends State { + /// Diisi bila server menjawab voucher habis / tidak tersedia; katalog + /// dimuat ulang saat kembali ke halaman sebelumnya. + String? _unavailableMessage; + + Voucher get _voucher => widget.voucher; + + Future _redeem() async { + final confirmed = await showWalletConfirmDialog( + context, + title: 'Tukar voucher?', + message: + 'Tukar ${_voucher.pointCost.thousandFormat} EnakPoint dengan ' + '${_voucher.name}? Tidak bisa dibatalkan.', + confirmLabel: 'Tukar', + ); + if (!confirmed || !mounted) return; + if (!await ensurePinReady(context) || !mounted) return; + + final success = await showPinSheet( + context, + title: 'Tukar voucher', + onSubmit: (pin) async { + final bloc = context.read(); + bloc.add( + VoucherRedeemFormEvent.submitted(voucherId: _voucher.id, pin: pin), + ); + final result = await bloc.stream.firstWhere((s) => !s.isSubmitting); + return result.failureOption.toNullable(); + }, + onFailure: _showFailure, + ); + + if (success && mounted) { + context.read().add(const WalletLoaderEvent.fetched()); + } + } + + void _showFailure(WalletFailure failure) { + if (failure.isPinError) return; + + if (failure.isNetworkError) { + AppFlushbar.showError( + context, + 'Koneksi terputus, status penukaran belum diketahui. Tekan Tukar lagi ' + 'untuk mengulang. EnakPoint tidak akan terpotong dua kali.', + ); + return; + } + + final description = describeVoucherRedeemFailure(failure); + if (description.reloadCatalog) { + setState(() => _unavailableMessage = description.message); + } + AppFlushbar.showError(context, description.message); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final result = state.result; + return PopScope( + canPop: result == null, + onPopInvokedWithResult: (didPop, _) { + if (!didPop && result != null) context.router.pop(true); + }, + child: Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar(title: const Text('Detail voucher')), + body: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: result != null + ? KeyedSubtree( + key: const ValueKey('success'), + child: _buildSuccess(result), + ) + : KeyedSubtree( + key: const ValueKey('detail'), + child: _buildDetail(state), + ), + ), + ), + ); + }, + ); + } + + Widget _buildDetail(VoucherRedeemFormState state) { + final faceValue = voucherFaceValueLabel(_voucher.type, _voucher.faceValue); + + return BlocBuilder( + builder: (context, walletState) { + final pointBalance = walletState.summary.pointBalance; + final canAfford = + !walletState.hasLoaded || pointBalance >= _voucher.pointCost; + + final String? blockedLabel = _unavailableMessage != null + ? 'Voucher tidak tersedia' + : _voucher.isOutOfStock + ? 'Habis' + : !canAfford + ? 'EnakPoint kurang' + : null; + + return Column( + children: [ + Expanded( + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular( + AppValue.borderRadius, + ), + ), + child: Column( + children: [ + VoucherImage( + url: _voucher.imageUrl, + size: 120, + dimmed: + _voucher.isOutOfStock || + _unavailableMessage != null, + ), + const SizedBox(height: 16), + Text( + voucherTypeLabel( + _voucher.type, + _voucher.faceValue, + ).toUpperCase(), + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w700, + letterSpacing: 0.6, + ), + ), + const SizedBox(height: 4), + Text( + _voucher.name, + textAlign: TextAlign.center, + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.w700, + ), + ), + if (faceValue != null) + Text( + faceValue, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + ), + ), + if (_voucher.description != null) ...[ + const SizedBox(height: 12), + Text( + _voucher.description!, + textAlign: TextAlign.center, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + height: 1.5, + ), + ), + ], + ], + ), + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular( + AppValue.borderRadius, + ), + ), + child: Column( + children: [ + _InfoRow( + label: 'Ditukar dengan', + value: + '${_voucher.pointCost.thousandFormat} EnakPoint', + ), + if (walletState.hasLoaded) + _InfoRow( + label: 'Saldo EnakPoint kamu', + value: pointBalance.thousandFormat, + valueColor: canAfford ? null : AppColor.error, + ), + if (_voucher.validUntil != null) + _InfoRow( + label: 'Bisa ditukar sampai', + value: formatWibDateTime(_voucher.validUntil!), + ), + if (_voucher.available != null) + _InfoRow( + label: 'Sisa stok', + value: _voucher.isOutOfStock + ? 'Habis' + : _voucher.available!.thousandFormat, + ), + if (_voucher.maxPerCustomer != null) + _InfoRow( + label: 'Batas tukar', + value: + '${_voucher.maxPerCustomer!.thousandFormat}× ' + 'per customer', + ), + ], + ), + ), + ], + ), + ), + Container( + color: AppColor.white, + padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), + child: SafeArea( + top: false, + child: AppElevatedButton( + title: + blockedLabel ?? + 'Tukar ${_voucher.pointCost.thousandFormat} EnakPoint', + height: 48, + isLoading: state.isSubmitting, + onPressed: blockedLabel == null && !state.isSubmitting + ? _redeem + : null, + ), + ), + ), + ], + ); + }, + ); + } + + Widget _buildSuccess(VoucherRedeemResult result) { + final redemption = result.redemption; + final isPending = redemption.status == VoucherRedemptionStatus.pending; + final isFailed = redemption.status == VoucherRedemptionStatus.failed; + + final (IconData icon, Color color, String title) = isPending + ? ( + Icons.hourglass_top_rounded, + AppColor.warning, + 'Voucher sedang diproses', + ) + : isFailed + ? (Icons.undo_rounded, AppColor.error, 'Penukaran gagal') + : (Icons.check_circle, AppColor.success, 'Voucher berhasil ditukar'); + + return SafeArea( + child: Column( + children: [ + Expanded( + child: ListView( + padding: const EdgeInsets.all(20), + children: [ + const SizedBox(height: 24), + Icon(icon, color: color, size: 72), + const SizedBox(height: 16), + Text( + title, + textAlign: TextAlign.center, + style: AppStyle.xl.copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 4), + Text( + redemption.voucherName, + textAlign: TextAlign.center, + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 24), + if (isPending) + _Note( + 'EnakPoint sudah terpotong. Cek lagi di Voucher saya. Bila ' + 'gagal, EnakPoint dikembalikan otomatis.', + ) + else if (isFailed) + _Note('EnakPoint sudah dikembalikan.') + else if (redemption.code != null) + VoucherCodeBox(code: redemption.code!) + else + const VoucherNoCodeNote(), + const SizedBox(height: 16), + if (!isPending && !isFailed && redemption.codeExpiresAt != null) + Text( + 'Berlaku sampai ' + '${formatWibDateTime(redemption.codeExpiresAt!)}', + textAlign: TextAlign.center, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 6), + Text( + 'Saldo EnakPoint sekarang: ' + '${result.pointBalance.thousandFormat}', + textAlign: TextAlign.center, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: AppElevatedButton( + title: 'Lihat voucher saya', + onPressed: () => context.router.pop(true), + ), + ), + ], + ), + ); + } +} + +class _Note extends StatelessWidget { + const _Note(this.message); + + final String message; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(AppValue.borderRadius), + ), + child: Text( + message, + textAlign: TextAlign.center, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ); + } +} + +class _InfoRow extends StatelessWidget { + const _InfoRow({required this.label, required this.value, this.valueColor}); + + final String label; + final String value; + final Color? valueColor; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: [ + Text( + label, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + value, + textAlign: TextAlign.end, + style: AppStyle.sm.copyWith( + color: valueColor ?? AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/reward/reward_page.dart b/lib/presentation/pages/reward/reward_page.dart index c7c4c02..10bd4d9 100644 --- a/lib/presentation/pages/reward/reward_page.dart +++ b/lib/presentation/pages/reward/reward_page.dart @@ -1,108 +1,281 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import '../../../application/voucher/voucher_loader/voucher_loader_bloc.dart'; +import '../../../application/voucher/voucher_redemption_loader/voucher_redemption_loader_bloc.dart'; +import '../../../application/wallet/wallet_loader/wallet_loader_bloc.dart'; +import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; -import '../../../sample/reward_sample_data.dart'; -import '../../components/assets/assets.gen.dart'; -import '../../components/delegate/filter_bar_delegate.dart'; +import '../../../domain/wallet/wallet.dart'; +import '../../../injection.dart'; +import '../../components/button/button.dart'; +import '../../components/toast/flushbar.dart'; import '../../router/app_router.gr.dart'; -import 'reward_ui.dart'; -import 'widgets/reward_card.dart'; -import 'widgets/reward_detail_sheet.dart'; - -enum _RewardFilter { - all('Semua'), - active('Aktif'), - used('Terpakai'), - expired('Kedaluwarsa'); - - const _RewardFilter(this.label); - - final String label; - - bool matches(Reward reward) => switch (this) { - _RewardFilter.all => true, - _RewardFilter.active => reward.status == RewardStatus.active, - _RewardFilter.used => reward.status == RewardStatus.used, - _RewardFilter.expired => reward.status == RewardStatus.expired, - }; -} +import '../wallet/wallet_ui.dart'; +import 'widgets/redemption_card.dart'; +import 'widgets/redemption_detail_sheet.dart'; +import 'widgets/voucher_card.dart'; +/// EnakReward: tukar EnakPoint ke voucher (§9.1) dan "Voucher saya" (§9.3). @RoutePage() -class RewardPage extends StatefulWidget { +class RewardPage extends StatefulWidget implements AutoRouteWrapper { const RewardPage({super.key}); @override State createState() => _RewardPageState(); + + @override + Widget wrappedRoute(BuildContext context) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => + getIt()..add(const VoucherLoaderEvent.fetched()), + ), + BlocProvider( + create: (_) => + getIt() + ..add(const VoucherRedemptionLoaderEvent.refreshed()), + ), + ], + child: this, + ); } -class _RewardPageState extends State { - _RewardFilter _filter = _RewardFilter.all; +class _RewardPageState extends State + with SingleTickerProviderStateMixin { + late final TabController _tabController = TabController( + length: 2, + vsync: this, + ); - /// Aktif lebih dulu, urut yang paling cepat berakhir; sisanya terbaru dulu. - final List _rewards = [...rewardSamples] - ..sort((a, b) { - final aActive = a.status == RewardStatus.active; - final bActive = b.status == RewardStatus.active; - if (aActive != bActive) return aActive ? -1 : 1; - if (aActive) return a.validUntil.compareTo(b.validUntil); - return b.redeemedAt.compareTo(a.redeemedAt); - }); + @override + void initState() { + super.initState(); + // Tombol Tukar nonaktif bila saldo EnakPoint kurang. + context.read().add(const WalletLoaderEvent.fetched()); + } - List get _activeRewards => - _rewards.where(_RewardFilter.active.matches).toList(); + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } - int _countOf(_RewardFilter filter) => _rewards.where(filter.matches).length; + /// Katalog, voucher saya, dan saldo dimuat ulang setiap kembali dari detail: + /// stok bisa berubah, dan voucher yang habis harus hilang dari katalog. + Future _openDetail(Voucher voucher) async { + final redeemed = await context.router.push( + RewardDetailRoute(voucher: voucher), + ); + if (!mounted) return; + + context.read().add(const VoucherLoaderEvent.fetched()); + context.read().add( + const VoucherRedemptionLoaderEvent.refreshed(), + ); + context.read().add(const WalletLoaderEvent.fetched()); + if (redeemed == true) _tabController.animateTo(1); + } @override Widget build(BuildContext context) { - final visible = _rewards.where(_filter.matches).toList(); - return Scaffold( backgroundColor: AppColor.background, - appBar: AppBar(title: const Text('Reward Saya')), - body: CustomScrollView( - slivers: [ - SliverToBoxAdapter(child: _buildSummary()), - SliverPersistentHeader( - pinned: true, - delegate: FilterBarDelegate<_RewardFilter>( - values: _RewardFilter.values, - selected: _filter, - labelOf: (filter) => filter.label, - countOf: _countOf, - onSelected: (filter) => setState(() => _filter = filter), - ), - ), - if (visible.isEmpty) - SliverFillRemaining(hasScrollBody: false, child: _buildEmpty()) - else - SliverPadding( - padding: const EdgeInsets.fromLTRB(16, 4, 16, 32), - sliver: SliverList.separated( - itemCount: visible.length, - separatorBuilder: (_, _) => const SizedBox(height: 12), - itemBuilder: (context, index) { - final reward = visible[index]; - return RewardCard( - reward: reward, - onTap: () => RewardDetailSheet.show(context, reward), - ); - }, - ), - ), + appBar: AppBar( + title: const Text('EnakReward'), + bottom: TabBar( + controller: _tabController, + tabs: const [ + Tab(text: 'Tukar voucher'), + Tab(text: 'Voucher saya'), + ], + ), + ), + body: TabBarView( + controller: _tabController, + children: [ + _CatalogTab(onOpen: _openDetail), + _RedemptionTab(onBrowse: () => _tabController.animateTo(0)), ], ), ); } +} - Widget _buildSummary() { - final active = _activeRewards; - // Sudah terurut: yang pertama adalah yang paling cepat berakhir. - final expiringSoon = active.where(isRewardExpiringSoon).firstOrNull; +class _CatalogTab extends StatelessWidget { + const _CatalogTab({required this.onOpen}); + + final ValueChanged onOpen; + + Future _onRefresh(BuildContext context) async { + context.read().add(const WalletLoaderEvent.fetched()); + final bloc = context.read(); + bloc.add(const VoucherLoaderEvent.fetched()); + await bloc.stream.firstWhere((s) => !s.isFetching); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (!state.hasLoaded) { + return state.failureOption.fold( + () => const Center( + child: SpinKitFadingCircle(color: AppColor.primary, size: 36), + ), + (f) => _ErrorView( + message: walletFailureMessage(f), + onRetry: () => context.read().add( + const VoucherLoaderEvent.fetched(), + ), + ), + ); + } + + return BlocBuilder( + builder: (context, walletState) => RefreshIndicator( + color: AppColor.primary, + onRefresh: () => _onRefresh(context), + child: ListView( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), + children: [ + _PointBalanceCard(walletState: walletState), + const SizedBox(height: 16), + if (state.vouchers.isEmpty) + const _EmptyView( + icon: Icons.confirmation_number_outlined, + message: 'Belum ada voucher yang bisa ditukar', + ) + else + ...state.vouchers.map( + (voucher) => Padding( + padding: const EdgeInsets.only(bottom: 12), + child: VoucherCard( + voucher: voucher, + canAfford: + !walletState.hasLoaded || + walletState.summary.pointBalance >= + voucher.pointCost, + onTap: () => onOpen(voucher), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } +} + +class _RedemptionTab extends StatelessWidget { + const _RedemptionTab({required this.onBrowse}); + + final VoidCallback onBrowse; + + Future _onRefresh(BuildContext context) async { + final bloc = context.read(); + bloc.add(const VoucherRedemptionLoaderEvent.refreshed()); + await bloc.stream.firstWhere((s) => !s.isFetching); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer< + VoucherRedemptionLoaderBloc, + VoucherRedemptionLoaderState + >( + listenWhen: (p, c) => + p.failureOption != c.failureOption && c.items.isNotEmpty, + listener: (context, state) => state.failureOption.fold( + () {}, + (f) => AppFlushbar.showError(context, walletFailureMessage(f)), + ), + builder: (context, state) { + if (!state.hasLoaded) { + return state.failureOption.fold( + () => const Center( + child: SpinKitFadingCircle(color: AppColor.primary, size: 36), + ), + (f) => _ErrorView( + message: walletFailureMessage(f), + onRetry: () => context.read().add( + const VoucherRedemptionLoaderEvent.refreshed(), + ), + ), + ); + } + + return RefreshIndicator( + color: AppColor.primary, + onRefresh: () => _onRefresh(context), + child: NotificationListener( + onNotification: (notification) { + final metrics = notification.metrics; + if (metrics.pixels >= metrics.maxScrollExtent - 200) { + context.read().add( + const VoucherRedemptionLoaderEvent.loadedMore(), + ); + } + return false; + }, + child: state.items.isEmpty + ? ListView( + padding: const EdgeInsets.all(16), + children: [ + _EmptyView( + icon: Icons.local_activity_outlined, + message: 'Belum ada voucher', + actionLabel: 'Tukar EnakPoint sekarang', + onAction: onBrowse, + ), + ], + ) + : ListView.separated( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), + itemCount: state.items.length + (state.hasMore ? 1 : 0), + separatorBuilder: (_, _) => const SizedBox(height: 12), + itemBuilder: (context, index) { + if (index >= state.items.length) { + return const Padding( + padding: EdgeInsets.all(16), + child: Center( + child: SpinKitFadingCircle( + color: AppColor.primary, + size: 24, + ), + ), + ); + } + final redemption = state.items[index]; + return RedemptionCard( + redemption: redemption, + onTap: () => + RedemptionDetailSheet.show(context, redemption), + ); + }, + ), + ), + ); + }, + ); + } +} + +/// Saldo EnakPoint, selalu "setara potongan", tidak pernah "saldo Rp". +class _PointBalanceCard extends StatelessWidget { + const _PointBalanceCard({required this.walletState}); + + final WalletLoaderState walletState; + + @override + Widget build(BuildContext context) { + final summary = walletState.summary; return Container( - margin: const EdgeInsets.fromLTRB(16, 16, 16, 4), padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( @@ -111,183 +284,90 @@ class _RewardPageState extends State { end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColor.primary.withOpacity(0.3), - blurRadius: 12, - offset: const Offset(0, 6), - ), - ], ), - child: Column( + child: Row( children: [ - Row( - children: [ - Container( - width: 48, - height: 48, - padding: const EdgeInsets.all(8), - decoration: const BoxDecoration( - color: AppColor.white, - shape: BoxShape.circle, - ), - child: Assets.icons.enakrewardIcon.image( - fit: BoxFit.contain, - // Aset aslinya besar; decode seukuran tampilan saja. - cacheWidth: (32 * MediaQuery.devicePixelRatioOf(context)) - .round(), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Siap dipakai', - style: AppStyle.sm.copyWith( - color: AppColor.textWhite.withOpacity(0.9), - ), - ), - FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text( - '${active.length} reward', - style: AppStyle.h5.copyWith( - color: AppColor.textWhite, - fontWeight: FontWeight.w700, - ), - ), - ), - ], - ), - ), - Material( - color: AppColor.white, - borderRadius: BorderRadius.circular(20), - child: InkWell( - borderRadius: BorderRadius.circular(20), - onTap: () => context.router.push(const CoinRoute()), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 8, - ), - child: Text( - 'Tukar Poin', - style: AppStyle.sm.copyWith( - color: AppColor.primary, - fontWeight: FontWeight.w700, - ), - ), - ), - ), - ), - ], - ), - if (expiringSoon != null) ...[ - const SizedBox(height: 14), - Material( - color: AppColor.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(10), - child: InkWell( - borderRadius: BorderRadius.circular(10), - onTap: () => RewardDetailSheet.show(context, expiringSoon), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - child: Row( - children: [ - const Icon( - Icons.schedule, - size: 16, - color: AppColor.textWhite, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - '${expiringSoon.name} · ' - '${rewardValidityLabel(expiringSoon).toLowerCase()}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: AppStyle.sm.copyWith( - color: AppColor.textWhite, - fontWeight: FontWeight.w600, - ), - ), - ), - const Icon( - Icons.chevron_right, - size: 18, - color: AppColor.textWhite, - ), - ], - ), - ), - ), + Container( + width: 44, + height: 44, + padding: const EdgeInsets.all(6), + decoration: const BoxDecoration( + color: AppColor.white, + shape: BoxShape.circle, ), - ], + child: const WalletCurrencyIcon( + currency: WalletCurrency.point, + size: 32, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'EnakPoint kamu', + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + ), + Text( + walletState.hasLoaded + ? summary.pointBalance.thousandFormat + : '…', + style: AppStyle.h5.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w700, + ), + ), + if (walletState.hasLoaded) + Text( + pointDiscountLabel(summary.pointDiscountValue), + style: AppStyle.xs.copyWith( + color: AppColor.textWhite.withOpacity(0.9), + ), + ), + ], + ), + ), ], ), ); } +} - Widget _buildEmpty() { - final (String emoji, String message) = switch (_filter) { - _RewardFilter.all => ('🎁', 'Belum ada reward'), - _RewardFilter.active => ('🎯', 'Belum ada reward aktif'), - _RewardFilter.used => ('✅', 'Belum ada reward yang dipakai'), - _RewardFilter.expired => ('⏰', 'Tidak ada reward yang kedaluwarsa'), - }; +class _EmptyView extends StatelessWidget { + const _EmptyView({ + required this.icon, + required this.message, + this.actionLabel, + this.onAction, + }); + final IconData icon; + final String message; + final String? actionLabel; + final VoidCallback? onAction; + + @override + Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.fromLTRB(32, 32, 32, 64), + padding: const EdgeInsets.only(top: 48), child: Column( - mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - width: 96, - height: 96, - alignment: Alignment.center, - decoration: BoxDecoration( - color: AppColor.primary.withOpacity(0.06), - shape: BoxShape.circle, - ), - child: Text(emoji, style: const TextStyle(fontSize: 44)), - ), - const SizedBox(height: 16), + Icon(icon, size: 56, color: AppColor.textLight), + const SizedBox(height: 12), Text( message, textAlign: TextAlign.center, - style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), + style: AppStyle.md.copyWith(color: AppColor.textSecondary), ), - const SizedBox(height: 6), - Text( - 'Tukar poin kamu untuk mendapatkan reward menarik', - textAlign: TextAlign.center, - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - if (_filter == _RewardFilter.all || - _filter == _RewardFilter.active) ...[ - const SizedBox(height: 20), - ElevatedButton( - onPressed: () => context.router.push(const CoinRoute()), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12, - ), - ), - child: Text( - 'Tukar Poin Sekarang', - style: AppStyle.sm.copyWith( - color: AppColor.white, - fontWeight: FontWeight.w700, - ), - ), + if (actionLabel != null) ...[ + const SizedBox(height: 16), + AppOutlineButton( + title: actionLabel!, + width: 220, + onPressed: onAction, ), ], ], @@ -295,3 +375,31 @@ class _RewardPageState extends State { ); } } + +class _ErrorView extends StatelessWidget { + const _ErrorView({required this.message, required this.onRetry}); + + final String message; + final VoidCallback onRetry; + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(message, style: AppStyle.md, textAlign: TextAlign.center), + const SizedBox(height: 16), + AppOutlineButton( + title: 'Coba lagi', + width: 160, + onPressed: onRetry, + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/reward/reward_ui.dart b/lib/presentation/pages/reward/reward_ui.dart index 08ab35e..cc89f7c 100644 --- a/lib/presentation/pages/reward/reward_ui.dart +++ b/lib/presentation/pages/reward/reward_ui.dart @@ -1,60 +1,50 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; +import 'package:flutter/services.dart'; +import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; -import '../../../sample/reward_sample_data.dart'; +import '../../../common/ui/painter/dashed_line_painter.dart'; +import '../../../domain/wallet/wallet.dart'; +import '../../components/toast/flushbar.dart'; +import '../wallet/wallet_ui.dart'; -/// Reward aktif dengan sisa masa berlaku segini dianggap segera berakhir. -const int rewardExpiringSoonDays = 7; +/// Label jenis voucher (§9.1). +String voucherTypeLabel(VoucherType type, int faceValue) => switch (type) { + VoucherType.fixedValue => 'Potongan ${faceValue.currencyFormatRp}', + VoucherType.percentage => 'Potongan persen', + VoucherType.freeItem => 'Gratis item', + VoucherType.merchantBenefit => 'Benefit merchant', + VoucherType.unknown => 'Voucher', +}; -extension RewardStatusUi on RewardStatus { +/// "senilai Rp 20.000". `null` untuk potongan nominal, karena nilainya sudah +/// ada di [voucherTypeLabel], dan bila server tidak mengirim nilai. +String? voucherFaceValueLabel(VoucherType type, int faceValue) => + type == VoucherType.fixedValue || faceValue <= 0 + ? null + : 'senilai ${faceValue.currencyFormatRp}'; + +extension VoucherRedemptionStatusUi on VoucherRedemptionStatus { String get label => switch (this) { - RewardStatus.active => 'Aktif', - RewardStatus.used => 'Terpakai', - RewardStatus.expired => 'Kedaluwarsa', + VoucherRedemptionStatus.completed => 'Siap dipakai', + VoucherRedemptionStatus.pending => 'Sedang diproses', + VoucherRedemptionStatus.failed => 'Gagal', + VoucherRedemptionStatus.unknown => '', }; Color get color => switch (this) { - RewardStatus.active => AppColor.success, - RewardStatus.used => AppColor.textSecondary, - RewardStatus.expired => AppColor.error, + VoucherRedemptionStatus.completed => AppColor.success, + VoucherRedemptionStatus.pending => AppColor.warning, + VoucherRedemptionStatus.failed => AppColor.error, + VoucherRedemptionStatus.unknown => AppColor.textSecondary, }; } -/// Tanggal pendek: "1 Okt 2026". -String formatRewardDate(DateTime date) => - DateFormat('d MMM yyyy', 'id_ID').format(date); +class VoucherRedemptionStatusBadge extends StatelessWidget { + const VoucherRedemptionStatusBadge({super.key, required this.status}); -/// Sisa hari kalender sampai [Reward.validUntil]; 0 berarti berakhir hari ini. -int rewardDaysLeft(Reward reward) { - final now = DateTime.now(); - final today = DateTime(now.year, now.month, now.day); - final end = DateTime( - reward.validUntil.year, - reward.validUntil.month, - reward.validUntil.day, - ); - return end.difference(today).inDays; -} - -bool isRewardExpiringSoon(Reward reward) => - reward.status == RewardStatus.active && - rewardDaysLeft(reward) <= rewardExpiringSoonDays; - -/// "Berakhir hari ini" / "Berakhir 3 hari lagi" / "Berlaku s/d 25 Okt 2026". -String rewardValidityLabel(Reward reward) { - final daysLeft = rewardDaysLeft(reward); - if (daysLeft <= 0) return 'Berakhir hari ini'; - if (daysLeft <= rewardExpiringSoonDays) { - return 'Berakhir $daysLeft hari lagi'; - } - return 'Berlaku s/d ${formatRewardDate(reward.validUntil)}'; -} - -class RewardStatusBadge extends StatelessWidget { - const RewardStatusBadge({super.key, required this.status}); - - final RewardStatus status; + final VoucherRedemptionStatus status; @override Widget build(BuildContext context) { @@ -75,13 +65,62 @@ class RewardStatusBadge extends StatelessWidget { } } -/// Emoji reward; abu-abu untuk reward yang sudah tidak bisa dipakai. -class RewardEmoji extends StatelessWidget { - const RewardEmoji({super.key, required this.reward, required this.size}); +/// Pesan untuk penolakan tukar voucher (§9.2). `cause` dari server berbahasa +/// Inggris dan tidak ditampilkan mentah. [reloadCatalog] menandai voucher yang +/// sudah tidak tersedia, sehingga katalog perlu dimuat ulang. +({String message, bool reloadCatalog}) describeVoucherRedeemFailure( + WalletFailure failure, +) { + final cause = failure.maybeMap( + rejected: (value) => value.cause.toLowerCase(), + orElse: () => null, + ); + if (cause == null) { + return (message: walletFailureMessage(failure), reloadCatalog: false); + } - final Reward reward; + if (cause.contains('not enough enakpoint')) { + return (message: 'EnakPoint kamu kurang.', reloadCatalog: false); + } + if (cause.contains('out of stock')) { + return (message: 'Voucher sudah habis.', reloadCatalog: true); + } + if (cause.contains('per customer')) { + return ( + message: 'Kamu sudah mencapai batas penukaran voucher ini.', + reloadCatalog: false, + ); + } + if (cause.contains('customer is not active')) { + return (message: 'Akun tidak aktif.', reloadCatalog: false); + } + if (cause.contains('not available') || + cause.contains('cannot be redeemed yet') || + cause.contains('has ended')) { + return (message: 'Voucher tidak tersedia.', reloadCatalog: true); + } + // Termasuk Idempotency-Key yang terpakai untuk voucher lain (bug di app). + return ( + message: 'Penukaran voucher tidak bisa diproses.', + reloadCatalog: false, + ); +} + +/// Gambar voucher dari `image_url`; ikon tiket bila kosong. +class VoucherImage extends StatelessWidget { + const VoucherImage({ + super.key, + required this.url, + required this.size, + this.dimmed = false, + }); + + final String? url; final double size; + /// Abu-abu untuk voucher yang tidak bisa ditukar / dipakai. + final bool dimmed; + static const ColorFilter _greyscale = ColorFilter.matrix([ 0.2126, 0.7152, 0.0722, 0, 0, // 0.2126, 0.7152, 0.0722, 0, 0, // @@ -91,8 +130,126 @@ class RewardEmoji extends StatelessWidget { @override Widget build(BuildContext context) { - final emoji = Text(reward.emoji, style: TextStyle(fontSize: size)); - if (reward.status == RewardStatus.active) return emoji; - return ColorFiltered(colorFilter: _greyscale, child: emoji); + final fallback = SizedBox( + width: size, + height: size, + child: Icon( + Icons.confirmation_number_outlined, + size: size * 0.7, + color: dimmed ? AppColor.textLight : AppColor.primary, + ), + ); + + final image = url == null + ? fallback + : ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: url!, + width: size, + height: size, + fit: BoxFit.cover, + placeholder: (_, _) => SizedBox(width: size, height: size), + errorWidget: (_, _, _) => fallback, + ), + ); + + if (!dimmed || url == null) return image; + return ColorFiltered(colorFilter: _greyscale, child: image); + } +} + +/// Kode voucher yang bisa disalin. Customer menunjukkan layar ini ke kasir. +class VoucherCodeBox extends StatelessWidget { + const VoucherCodeBox({super.key, required this.code}); + + final String code; + + Future _copy(BuildContext context) async { + await Clipboard.setData(ClipboardData(text: code)); + if (!context.mounted) return; + AppFlushbar.showSuccess(context, 'Kode voucher disalin'); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 14, 8, 8), + decoration: BoxDecoration( + border: Border.all(color: AppColor.border), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + Text( + 'Tunjukkan kode ini ke kasir', + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 10), + CustomPaint( + size: const Size(double.infinity, 1), + painter: DashedLinePainter(), + ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: SelectableText( + code, + textAlign: TextAlign.center, + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.w700, + letterSpacing: 2, + ), + ), + ), + IconButton( + onPressed: () => _copy(context), + tooltip: 'Salin kode', + icon: const Icon( + Icons.copy_rounded, + size: 20, + color: AppColor.primary, + ), + ), + ], + ), + ], + ), + ); + } +} + +/// Catatan untuk voucher tanpa kode (`code` null): customer menunjukkan layar +/// voucher ke kasir. +class VoucherNoCodeNote extends StatelessWidget { + const VoucherNoCodeNote({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppColor.successWithOpacity(0.08), + borderRadius: BorderRadius.circular(AppValue.borderRadius), + ), + child: Row( + children: [ + const Icon( + Icons.storefront_outlined, + size: 20, + color: AppColor.success, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + 'Voucher ini tidak memakai kode. Tunjukkan layar ini ke kasir.', + style: AppStyle.sm, + ), + ), + ], + ), + ); } } diff --git a/lib/presentation/pages/reward/widgets/redemption_card.dart b/lib/presentation/pages/reward/widgets/redemption_card.dart new file mode 100644 index 0000000..8df7d4b --- /dev/null +++ b/lib/presentation/pages/reward/widgets/redemption_card.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../domain/wallet/wallet.dart'; +import '../../wallet/wallet_ui.dart'; +import '../reward_ui.dart'; +import 'ticket_card.dart'; + +/// Satu voucher di "Voucher saya" (§9.3). +class RedemptionCard extends StatelessWidget { + const RedemptionCard({ + super.key, + required this.redemption, + required this.onTap, + }); + + final VoucherRedemption redemption; + final VoidCallback onTap; + + bool get _isReady => redemption.status == VoucherRedemptionStatus.completed; + + @override + Widget build(BuildContext context) { + final status = redemption.status; + + return TicketCard( + active: _isReady, + onTap: onTap, + stub: TicketStub( + image: VoucherImage( + url: redemption.voucherImageUrl, + size: 44, + dimmed: !_isReady, + ), + pointCost: redemption.pointCost.thousandFormat, + active: _isReady, + ), + body: Padding( + padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + voucherTypeLabel( + redemption.voucherType, + redemption.faceValue, + ).toUpperCase(), + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w700, + letterSpacing: 0.6, + ), + ), + ), + if (status != VoucherRedemptionStatus.unknown) + VoucherRedemptionStatusBadge(status: status), + ], + ), + const SizedBox(height: 4), + Text( + redemption.voucherName, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.lg.copyWith( + color: _isReady ? AppColor.textPrimary : AppColor.textSecondary, + fontWeight: FontWeight.w700, + ), + ), + if (_isReady && redemption.code != null) ...[ + const SizedBox(height: 2), + Text( + redemption.code!, + style: AppStyle.sm.copyWith( + fontWeight: FontWeight.w700, + letterSpacing: 1, + ), + ), + ], + const SizedBox(height: 8), + _buildFootnote(), + ], + ), + ), + ); + } + + Widget _buildFootnote() { + final (IconData icon, String text) = switch (redemption.status) { + VoucherRedemptionStatus.completed => ( + Icons.schedule, + redemption.codeExpiresAt == null + ? 'Tunjukkan ke kasir' + : 'Berlaku sampai ${formatWibDate(redemption.codeExpiresAt!)}', + ), + VoucherRedemptionStatus.pending => ( + Icons.hourglass_top_rounded, + 'Voucher sedang diproses', + ), + VoucherRedemptionStatus.failed => ( + Icons.undo_rounded, + 'EnakPoint sudah dikembalikan', + ), + VoucherRedemptionStatus.unknown => ( + Icons.info_outline, + redemption.createdAt == null + ? '' + : 'Ditukar ${formatWibDate(redemption.createdAt!)}', + ), + }; + + return Row( + children: [ + Icon(icon, size: 14, color: AppColor.textSecondary), + const SizedBox(width: 4), + Expanded( + child: Text( + text, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/pages/reward/widgets/redemption_detail_sheet.dart b/lib/presentation/pages/reward/widgets/redemption_detail_sheet.dart new file mode 100644 index 0000000..4cc6ef3 --- /dev/null +++ b/lib/presentation/pages/reward/widgets/redemption_detail_sheet.dart @@ -0,0 +1,210 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../domain/wallet/wallet.dart'; +import '../../wallet/wallet_ui.dart'; +import '../reward_ui.dart'; + +/// Detail voucher milik customer. Untuk memakai voucher, customer +/// menunjukkan layar ini ke kasir; POS belum bisa menandai voucher terpakai. +class RedemptionDetailSheet extends StatelessWidget { + const RedemptionDetailSheet({super.key, required this.redemption}); + + final VoucherRedemption redemption; + + static Future show(BuildContext context, VoucherRedemption redemption) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: AppColor.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (_) => RedemptionDetailSheet(redemption: redemption), + ); + } + + @override + Widget build(BuildContext context) { + final faceValue = voucherFaceValueLabel( + redemption.voucherType, + redemption.faceValue, + ); + + return ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.85, + ), + child: SafeArea( + top: false, + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(20, 12, 20, 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.border, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 20), + _buildHeader(), + const SizedBox(height: 20), + _buildStatusContent(), + const SizedBox(height: 20), + if (redemption.createdAt != null) + _InfoRow( + label: 'Ditukar pada', + value: formatWibDateTime(redemption.createdAt!), + ), + if (redemption.status == VoucherRedemptionStatus.completed && + redemption.codeExpiresAt != null) + _InfoRow( + label: 'Berlaku sampai', + value: formatWibDateTime(redemption.codeExpiresAt!), + ), + _InfoRow( + label: 'EnakPoint ditukar', + value: redemption.pointCost.thousandFormat, + ), + if (faceValue != null) + _InfoRow(label: 'Nilai voucher', value: faceValue), + ], + ), + ), + ), + ); + } + + Widget _buildHeader() { + final status = redemption.status; + return Row( + children: [ + Container( + width: 56, + height: 56, + alignment: Alignment.center, + decoration: BoxDecoration( + color: AppColor.primary.withOpacity(0.06), + borderRadius: BorderRadius.circular(14), + ), + child: VoucherImage(url: redemption.voucherImageUrl, size: 40), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + voucherTypeLabel( + redemption.voucherType, + redemption.faceValue, + ).toUpperCase(), + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w700, + letterSpacing: 0.6, + ), + ), + const SizedBox(height: 2), + Text( + redemption.voucherName, + style: AppStyle.xl.copyWith(fontWeight: FontWeight.w700), + ), + ], + ), + ), + if (status != VoucherRedemptionStatus.unknown) ...[ + const SizedBox(width: 8), + VoucherRedemptionStatusBadge(status: status), + ], + ], + ); + } + + Widget _buildStatusContent() { + switch (redemption.status) { + case VoucherRedemptionStatus.completed: + return redemption.code == null + ? const VoucherNoCodeNote() + : VoucherCodeBox(code: redemption.code!); + case VoucherRedemptionStatus.pending: + return const _Note( + icon: Icons.hourglass_top_rounded, + color: AppColor.warning, + message: + 'Voucher sedang diproses. EnakPoint sudah terpotong; cek lagi ' + 'nanti.', + ); + case VoucherRedemptionStatus.failed: + return const _Note( + icon: Icons.undo_rounded, + color: AppColor.error, + message: 'Gagal, EnakPoint sudah dikembalikan.', + ); + case VoucherRedemptionStatus.unknown: + return const SizedBox.shrink(); + } + } +} + +class _Note extends StatelessWidget { + const _Note({required this.icon, required this.color, required this.message}); + + final IconData icon; + final Color color; + final String message; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppColor.background, + borderRadius: BorderRadius.circular(AppValue.borderRadius), + ), + child: Row( + children: [ + Icon(icon, size: 20, color: color), + const SizedBox(width: 10), + Expanded( + child: Text( + message, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + ), + ], + ), + ); + } +} + +class _InfoRow extends StatelessWidget { + const _InfoRow({required this.label, required this.value}); + + final String label; + final String value; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + Text( + label, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const Spacer(), + Text(value, style: AppStyle.sm.copyWith(fontWeight: FontWeight.w600)), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/reward/widgets/reward_card.dart b/lib/presentation/pages/reward/widgets/reward_card.dart deleted file mode 100644 index 4b436c3..0000000 --- a/lib/presentation/pages/reward/widgets/reward_card.dart +++ /dev/null @@ -1,249 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../../common/extension/extension.dart'; -import '../../../../common/theme/theme.dart'; -import '../../../../sample/reward_sample_data.dart'; -import '../reward_ui.dart'; - -/// Kartu reward berbentuk tiket: sobekan kiri (emoji + poin) dan isi kanan. -class RewardCard extends StatelessWidget { - const RewardCard({super.key, required this.reward, required this.onTap}); - - final Reward reward; - final VoidCallback onTap; - - static const double _stubWidth = 84; - - bool get _isActive => reward.status == RewardStatus.active; - - @override - Widget build(BuildContext context) { - return PhysicalShape( - clipper: const _TicketClipper(notchX: _stubWidth + 0.5), - color: AppColor.white, - elevation: 2, - shadowColor: Colors.black.withOpacity(0.25), - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: onTap, - child: IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildStub(), - const Padding( - padding: EdgeInsets.symmetric(vertical: 14), - child: CustomPaint( - size: Size(1, double.infinity), - painter: _VerticalDashPainter(), - ), - ), - Expanded(child: _buildBody()), - ], - ), - ), - ), - ), - ); - } - - Widget _buildStub() { - return Container( - width: _stubWidth, - color: _isActive - ? AppColor.primary.withOpacity(0.06) - : AppColor.background, - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RewardEmoji(reward: reward, size: 34), - const SizedBox(height: 8), - Text( - reward.pointsUsed.thousandFormat, - style: AppStyle.md.copyWith( - color: _isActive ? AppColor.primary : AppColor.textSecondary, - fontWeight: FontWeight.w700, - ), - ), - Text( - 'poin', - style: AppStyle.xs.copyWith(color: AppColor.textSecondary), - ), - ], - ), - ); - } - - Widget _buildBody() { - return Padding( - padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - reward.categoryName.toUpperCase(), - style: AppStyle.xs.copyWith( - color: AppColor.textSecondary, - fontWeight: FontWeight.w700, - letterSpacing: 0.6, - ), - ), - ), - RewardStatusBadge(status: reward.status), - ], - ), - const SizedBox(height: 4), - Text( - reward.name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: AppStyle.lg.copyWith( - color: _isActive ? AppColor.textPrimary : AppColor.textSecondary, - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 2), - Text( - reward.description, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - const SizedBox(height: 10), - Row( - children: [ - Expanded(child: _buildFootnote()), - if (_isActive) ...[ - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 6, - ), - decoration: BoxDecoration( - color: AppColor.primary, - borderRadius: BorderRadius.circular(20), - ), - child: Text( - 'Pakai', - style: AppStyle.sm.copyWith( - color: AppColor.white, - fontWeight: FontWeight.w700, - ), - ), - ), - ], - ], - ), - ], - ), - ); - } - - Widget _buildFootnote() { - final (IconData icon, String text, Color color) = switch (reward.status) { - RewardStatus.active => ( - Icons.schedule, - rewardValidityLabel(reward), - isRewardExpiringSoon(reward) - ? AppColor.warning - : AppColor.textSecondary, - ), - RewardStatus.used => ( - Icons.check_circle_outline, - reward.usedAt == null - ? 'Sudah dipakai' - : 'Dipakai ${formatRewardDate(reward.usedAt!)}', - AppColor.textSecondary, - ), - RewardStatus.expired => ( - Icons.event_busy_outlined, - 'Berakhir ${formatRewardDate(reward.validUntil)}', - AppColor.textSecondary, - ), - }; - - return Row( - children: [ - Icon(icon, size: 14, color: color), - const SizedBox(width: 4), - Expanded( - child: Text( - text, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: AppStyle.xs.copyWith( - color: color, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ); - } -} - -/// Persegi panjang membulat dengan lekuk setengah lingkaran di atas dan -/// bawah pada posisi [notchX], tempat garis sobekan tiket. -class _TicketClipper extends CustomClipper { - const _TicketClipper({required this.notchX}); - - final double notchX; - - static const double _radius = 16; - static const double _notchRadius = 8; - - @override - Path getClip(Size size) { - final card = Path() - ..addRRect( - RRect.fromRectAndRadius( - Offset.zero & size, - const Radius.circular(_radius), - ), - ); - final notches = Path() - ..addOval( - Rect.fromCircle(center: Offset(notchX, 0), radius: _notchRadius), - ) - ..addOval( - Rect.fromCircle( - center: Offset(notchX, size.height), - radius: _notchRadius, - ), - ); - return Path.combine(PathOperation.difference, card, notches); - } - - @override - bool shouldReclip(covariant _TicketClipper oldClipper) => - oldClipper.notchX != notchX; -} - -class _VerticalDashPainter extends CustomPainter { - const _VerticalDashPainter(); - - static const double _dash = 5; - static const double _gap = 4; - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = AppColor.border - ..strokeWidth = size.width; - final x = size.width / 2; - - for (double y = 0; y < size.height; y += _dash + _gap) { - final end = (y + _dash).clamp(0, size.height).toDouble(); - canvas.drawLine(Offset(x, y), Offset(x, end), paint); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} diff --git a/lib/presentation/pages/reward/widgets/reward_detail_sheet.dart b/lib/presentation/pages/reward/widgets/reward_detail_sheet.dart deleted file mode 100644 index 0a5430e..0000000 --- a/lib/presentation/pages/reward/widgets/reward_detail_sheet.dart +++ /dev/null @@ -1,280 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:qr_flutter/qr_flutter.dart'; - -import '../../../../common/extension/extension.dart'; -import '../../../../common/theme/theme.dart'; -import '../../../../common/ui/painter/dashed_line_painter.dart'; -import '../../../../sample/reward_sample_data.dart'; -import '../../../components/toast/flushbar.dart'; -import '../reward_ui.dart'; - -class RewardDetailSheet extends StatelessWidget { - const RewardDetailSheet({super.key, required this.reward}); - - final Reward reward; - - static Future show(BuildContext context, Reward reward) { - return showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: AppColor.white, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), - builder: (_) => RewardDetailSheet(reward: reward), - ); - } - - bool get _isActive => reward.status == RewardStatus.active; - - @override - Widget build(BuildContext context) { - return ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.sizeOf(context).height * 0.85, - ), - child: SafeArea( - top: false, - child: SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(20, 12, 20, 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: AppColor.border, - borderRadius: BorderRadius.circular(2), - ), - ), - ), - const SizedBox(height: 20), - _buildHeader(), - const SizedBox(height: 20), - if (_isActive && reward.couponCode != null) - _CouponBox(code: reward.couponCode!) - else - _buildInactiveNote(), - const SizedBox(height: 20), - _InfoRow( - label: 'Ditukar pada', - value: formatRewardDate(reward.redeemedAt), - ), - _InfoRow( - label: 'Berlaku sampai', - value: formatRewardDate(reward.validUntil), - valueColor: isRewardExpiringSoon(reward) - ? AppColor.warning - : null, - ), - if (reward.usedAt != null) - _InfoRow( - label: 'Dipakai pada', - value: formatRewardDate(reward.usedAt!), - ), - _InfoRow( - label: 'Poin ditukar', - value: '${reward.pointsUsed.thousandFormat} poin', - ), - if (reward.termsAndConditions != null) ...[ - const SizedBox(height: 16), - Text( - 'Syarat & Ketentuan', - style: AppStyle.md.copyWith(fontWeight: FontWeight.w700), - ), - const SizedBox(height: 6), - Text( - reward.termsAndConditions!, - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - height: 1.5, - ), - ), - ], - ], - ), - ), - ), - ); - } - - Widget _buildHeader() { - return Row( - children: [ - Container( - width: 56, - height: 56, - alignment: Alignment.center, - decoration: BoxDecoration( - color: _isActive - ? AppColor.primary.withOpacity(0.06) - : AppColor.background, - borderRadius: BorderRadius.circular(14), - ), - child: RewardEmoji(reward: reward, size: 28), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - reward.categoryName.toUpperCase(), - style: AppStyle.xs.copyWith( - color: AppColor.textSecondary, - fontWeight: FontWeight.w700, - letterSpacing: 0.6, - ), - ), - const SizedBox(height: 2), - Text( - reward.name, - style: AppStyle.xl.copyWith(fontWeight: FontWeight.w700), - ), - Text( - reward.description, - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - ], - ), - ), - const SizedBox(width: 8), - RewardStatusBadge(status: reward.status), - ], - ); - } - - Widget _buildInactiveNote() { - final message = switch (reward.status) { - RewardStatus.used => - reward.usedAt == null - ? 'Reward ini sudah dipakai.' - : 'Reward ini sudah dipakai pada ' - '${formatRewardDate(reward.usedAt!)}.', - _ => - 'Masa berlaku reward ini sudah habis pada ' - '${formatRewardDate(reward.validUntil)}.', - }; - - return Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: AppColor.background, - borderRadius: BorderRadius.circular(AppValue.borderRadius), - ), - child: Row( - children: [ - Icon(Icons.info_outline, size: 20, color: reward.status.color), - const SizedBox(width: 10), - Expanded( - child: Text( - message, - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - ), - ], - ), - ); - } -} - -class _CouponBox extends StatelessWidget { - const _CouponBox({required this.code}); - - final String code; - - Future _copy(BuildContext context) async { - await Clipboard.setData(ClipboardData(text: code)); - if (!context.mounted) return; - AppFlushbar.showSuccess(context, 'Kode reward disalin'); - } - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border.all(color: AppColor.border), - borderRadius: BorderRadius.circular(16), - ), - child: Column( - children: [ - QrImageView( - data: code, - size: 168, - padding: EdgeInsets.zero, - backgroundColor: AppColor.white, - ), - const SizedBox(height: 12), - Text( - 'Tunjukkan QR atau kode ini ke kasir', - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - const SizedBox(height: 14), - CustomPaint( - size: const Size(double.infinity, 1), - painter: DashedLinePainter(), - ), - const SizedBox(height: 6), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Text( - code, - style: AppStyle.xl.copyWith( - fontWeight: FontWeight.w700, - letterSpacing: 2, - ), - ), - ), - IconButton( - onPressed: () => _copy(context), - tooltip: 'Salin kode', - icon: const Icon( - Icons.copy_rounded, - size: 20, - color: AppColor.primary, - ), - ), - ], - ), - ], - ), - ); - } -} - -class _InfoRow extends StatelessWidget { - const _InfoRow({required this.label, required this.value, this.valueColor}); - - final String label; - final String value; - final Color? valueColor; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - children: [ - Text( - label, - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - const Spacer(), - Text( - value, - style: AppStyle.sm.copyWith( - color: valueColor ?? AppColor.textPrimary, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/pages/reward/widgets/ticket_card.dart b/lib/presentation/pages/reward/widgets/ticket_card.dart new file mode 100644 index 0000000..9d74426 --- /dev/null +++ b/lib/presentation/pages/reward/widgets/ticket_card.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +/// Kartu berbentuk tiket: sobekan kiri ([stub]) dan isi kanan ([body]), +/// dipisah garis putus-putus. +class TicketCard extends StatelessWidget { + const TicketCard({ + super.key, + required this.stub, + required this.body, + required this.onTap, + this.active = true, + }); + + final Widget stub; + final Widget body; + final VoidCallback onTap; + + /// Latar sobekan berwarna untuk voucher yang bisa ditukar / dipakai. + final bool active; + + static const double stubWidth = 84; + + @override + Widget build(BuildContext context) { + return PhysicalShape( + clipper: const _TicketClipper(notchX: stubWidth + 0.5), + color: AppColor.white, + elevation: 2, + shadowColor: Colors.black.withOpacity(0.25), + child: Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTap, + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + width: stubWidth, + color: active + ? AppColor.primary.withOpacity(0.06) + : AppColor.background, + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 16, + ), + child: stub, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 14), + child: CustomPaint( + size: Size(1, double.infinity), + painter: _VerticalDashPainter(), + ), + ), + Expanded(child: body), + ], + ), + ), + ), + ), + ); + } +} + +/// Persegi panjang membulat dengan lekuk setengah lingkaran di atas dan +/// bawah pada posisi [notchX], tempat garis sobekan tiket. +class _TicketClipper extends CustomClipper { + const _TicketClipper({required this.notchX}); + + final double notchX; + + static const double _radius = 16; + static const double _notchRadius = 8; + + @override + Path getClip(Size size) { + final card = Path() + ..addRRect( + RRect.fromRectAndRadius( + Offset.zero & size, + const Radius.circular(_radius), + ), + ); + final notches = Path() + ..addOval( + Rect.fromCircle(center: Offset(notchX, 0), radius: _notchRadius), + ) + ..addOval( + Rect.fromCircle( + center: Offset(notchX, size.height), + radius: _notchRadius, + ), + ); + return Path.combine(PathOperation.difference, card, notches); + } + + @override + bool shouldReclip(covariant _TicketClipper oldClipper) => + oldClipper.notchX != notchX; +} + +class _VerticalDashPainter extends CustomPainter { + const _VerticalDashPainter(); + + static const double _dash = 5; + static const double _gap = 4; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = AppColor.border + ..strokeWidth = size.width; + final x = size.width / 2; + + for (double y = 0; y < size.height; y += _dash + _gap) { + final end = (y + _dash).clamp(0, size.height).toDouble(); + canvas.drawLine(Offset(x, y), Offset(x, end), paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +/// Isi sobekan: gambar voucher dan EnakPoint yang ditukar. +class TicketStub extends StatelessWidget { + const TicketStub({ + super.key, + required this.image, + required this.pointCost, + this.active = true, + }); + + final Widget image; + final String pointCost; + final bool active; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + image, + const SizedBox(height: 8), + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + pointCost, + style: AppStyle.md.copyWith( + color: active ? AppColor.primary : AppColor.textSecondary, + fontWeight: FontWeight.w700, + ), + ), + ), + Text( + 'EnakPoint', + style: AppStyle.xs.copyWith( + fontSize: 10, + color: AppColor.textSecondary, + ), + ), + ], + ); + } +} diff --git a/lib/presentation/pages/reward/widgets/voucher_card.dart b/lib/presentation/pages/reward/widgets/voucher_card.dart new file mode 100644 index 0000000..0c7d9ef --- /dev/null +++ b/lib/presentation/pages/reward/widgets/voucher_card.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../domain/wallet/wallet.dart'; +import '../../wallet/wallet_ui.dart'; +import '../reward_ui.dart'; +import 'ticket_card.dart'; + +/// Voucher di katalog (§9.1). +class VoucherCard extends StatelessWidget { + const VoucherCard({ + super.key, + required this.voucher, + required this.canAfford, + required this.onTap, + }); + + final Voucher voucher; + + /// Saldo EnakPoint cukup untuk [Voucher.pointCost]. + final bool canAfford; + final VoidCallback onTap; + + bool get _canRedeem => canAfford && !voucher.isOutOfStock; + + @override + Widget build(BuildContext context) { + final faceValue = voucherFaceValueLabel(voucher.type, voucher.faceValue); + + return TicketCard( + active: _canRedeem, + onTap: onTap, + stub: TicketStub( + image: VoucherImage( + url: voucher.imageUrl, + size: 44, + dimmed: voucher.isOutOfStock, + ), + pointCost: voucher.pointCost.thousandFormat, + active: _canRedeem, + ), + body: Padding( + padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + voucherTypeLabel(voucher.type, voucher.faceValue).toUpperCase(), + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w700, + letterSpacing: 0.6, + ), + ), + const SizedBox(height: 4), + Text( + voucher.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), + ), + if (faceValue != null) + Text( + faceValue, + style: AppStyle.sm.copyWith(color: AppColor.textSecondary), + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded(child: _buildFootnote()), + const SizedBox(width: 8), + _buildAction(), + ], + ), + ], + ), + ), + ); + } + + Widget _buildFootnote() { + final parts = [ + if (voucher.validUntil != null) + 's/d ${formatWibDate(voucher.validUntil!)}', + if (voucher.available != null && !voucher.isOutOfStock) + 'sisa ${voucher.available!.thousandFormat}', + ]; + if (parts.isEmpty) return const SizedBox.shrink(); + + return Text( + parts.join(' · '), + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: AppStyle.xs.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w600, + ), + ); + } + + Widget _buildAction() { + final (String label, bool enabled) = voucher.isOutOfStock + ? ('Habis', false) + : canAfford + ? ('Tukar', true) + : ('Poin kurang', false); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), + decoration: BoxDecoration( + color: enabled ? AppColor.primary : AppColor.background, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + label, + style: AppStyle.sm.copyWith( + color: enabled ? AppColor.white : AppColor.textSecondary, + fontWeight: FontWeight.w700, + ), + ), + ); + } +} diff --git a/lib/presentation/pages/wallet/exchange_page.dart b/lib/presentation/pages/wallet/exchange_page.dart index 504839e..ab42e3e 100644 --- a/lib/presentation/pages/wallet/exchange_page.dart +++ b/lib/presentation/pages/wallet/exchange_page.dart @@ -22,7 +22,7 @@ import 'widgets/wallet_confirm_dialog.dart'; import 'widgets/wallet_history_section.dart'; import 'widgets/wallet_result_view.dart'; -/// Tukar EnakCoin → EnakPoint (§8.1). +/// Tukar EnakCoin → EnakPoint (§7.1). @RoutePage() class ExchangePage extends StatefulWidget implements AutoRouteWrapper { const ExchangePage({super.key}); diff --git a/lib/presentation/pages/wallet/payment_code_page.dart b/lib/presentation/pages/wallet/payment_code_page.dart deleted file mode 100644 index 63ae7c8..0000000 --- a/lib/presentation/pages/wallet/payment_code_page.dart +++ /dev/null @@ -1,226 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:qr_flutter/qr_flutter.dart'; - -import '../../../application/wallet/payment_code_form/payment_code_form_bloc.dart'; -import '../../../application/wallet/wallet_loader/wallet_loader_bloc.dart'; -import '../../../common/extension/extension.dart'; -import '../../../common/theme/theme.dart'; -import '../../../domain/wallet/wallet.dart'; -import '../../../injection.dart'; -import '../../components/button/button.dart'; -import '../../components/toast/flushbar.dart'; -import '../pin/pin_guard.dart'; -import '../pin/widgets/pin_sheet.dart'; -import 'wallet_ui.dart'; - -/// Bayar di kasir dengan kode bayar (§7.1). -@RoutePage() -class PaymentCodePage extends StatefulWidget implements AutoRouteWrapper { - const PaymentCodePage({super.key}); - - @override - State createState() => _PaymentCodePageState(); - - @override - Widget wrappedRoute(BuildContext context) => - BlocProvider(create: (_) => getIt(), child: this); -} - -class _PaymentCodePageState extends State { - Timer? _ticker; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) => _generate()); - _ticker = Timer.periodic(const Duration(seconds: 1), (_) => _tick()); - } - - @override - void dispose() { - _ticker?.cancel(); - super.dispose(); - } - - void _tick() { - if (!mounted) return; - final code = context.read().state.paymentCode; - if (code == null) return; - - if (!DateTime.now().isBefore(code.expiresAt)) { - context.read().add( - const PaymentCodeFormEvent.expired(), - ); - } else { - setState(() {}); - } - } - - Future _generate() async { - if (!await ensurePinReady(context) || !mounted) return; - - await showPinSheet( - context, - title: 'Bayar di kasir', - subtitle: 'Masukkan PIN untuk menampilkan kode bayar', - onSubmit: (pin) async { - final bloc = context.read(); - bloc.add(PaymentCodeFormEvent.submitted(pin)); - final state = await bloc.stream.firstWhere((s) => !s.isSubmitting); - return state.failureOption.toNullable(); - }, - onFailure: (f) => AppFlushbar.showError(context, walletFailureMessage(f)), - ); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final code = state.paymentCode; - - return Scaffold( - backgroundColor: AppColor.background, - appBar: AppBar(title: const Text('Bayar di Kasir')), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - _buildBalance(), - const SizedBox(height: 16), - code == null ? _buildEmpty(state.isSubmitting) : _buildCode(code), - const SizedBox(height: 16), - Text( - 'Tunjukkan kode ini ke kasir. Kasir yang memilih jumlah ' - 'EnakPoint yang dipakai. Kode hanya bisa dipakai sekali, dan ' - 'membuat kode baru membatalkan kode sebelumnya.', - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - textAlign: TextAlign.center, - ), - ], - ), - ); - }, - ); - } - - Widget _buildBalance() { - return BlocBuilder( - builder: (context, state) { - final summary = state.summary; - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(AppValue.borderRadius), - ), - child: Row( - children: [ - const WalletCurrencyIcon( - currency: WalletCurrency.point, - size: 28, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${summary.pointBalance.thousandFormat} EnakPoint', - style: AppStyle.lg.copyWith(fontWeight: FontWeight.w700), - ), - Text( - pointDiscountLabel(summary.pointDiscountValue), - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - ), - ), - ], - ), - ), - ], - ), - ); - }, - ); - } - - Widget _buildEmpty(bool isSubmitting) { - return Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(AppValue.borderRadius), - ), - child: Column( - children: [ - const Icon(Icons.qr_code_2, size: 72, color: AppColor.textLight), - const SizedBox(height: 12), - Text( - 'Kode bayar belum dibuat atau sudah kedaluwarsa', - textAlign: TextAlign.center, - style: AppStyle.md.copyWith(color: AppColor.textSecondary), - ), - const SizedBox(height: 16), - AppElevatedButton( - title: 'Buat kode baru', - isLoading: isSubmitting, - onPressed: isSubmitting ? null : _generate, - ), - ], - ), - ); - } - - Widget _buildCode(PaymentCode code) { - final remaining = code.expiresAt.difference(DateTime.now()); - final spacedCode = code.code.split('').join(' '); - - return Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(AppValue.borderRadius), - ), - child: Column( - children: [ - Text( - spacedCode, - style: AppStyle.h1.copyWith( - fontWeight: FontWeight.w700, - letterSpacing: 2, - ), - ), - const SizedBox(height: 16), - QrImageView( - data: code.qrPayload, - size: 220, - backgroundColor: AppColor.white, - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.timer_outlined, - size: 18, - color: AppColor.warning, - ), - const SizedBox(width: 6), - Text( - 'Berlaku ${formatCountdown(remaining)}', - style: AppStyle.md.copyWith( - color: AppColor.warning, - fontWeight: FontWeight.w700, - ), - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/presentation/pages/wallet/transfer_page.dart b/lib/presentation/pages/wallet/transfer_page.dart index f96482b..7db4a73 100644 --- a/lib/presentation/pages/wallet/transfer_page.dart +++ b/lib/presentation/pages/wallet/transfer_page.dart @@ -20,7 +20,7 @@ import 'widgets/wallet_history_section.dart'; import 'widgets/wallet_confirm_dialog.dart'; import 'widgets/wallet_result_view.dart'; -/// Transfer EnakPoint / EnakCoin ke customer lain (§8.2). +/// Transfer EnakPoint / EnakCoin ke customer lain (§7.2). @RoutePage() class TransferPage extends StatefulWidget implements AutoRouteWrapper { const TransferPage({super.key}); diff --git a/lib/presentation/pages/wallet/wallet_history_page.dart b/lib/presentation/pages/wallet/wallet_history_page.dart index 6fef883..4ba212e 100644 --- a/lib/presentation/pages/wallet/wallet_history_page.dart +++ b/lib/presentation/pages/wallet/wallet_history_page.dart @@ -83,6 +83,7 @@ class _WalletHistoryPageState extends State borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => _FilterSheet( + currency: state.currency, initial: _HistoryFilter( types: state.types, from: state.from, @@ -264,8 +265,10 @@ class _HistoryFilter { } class _FilterSheet extends StatefulWidget { - const _FilterSheet({required this.initial}); + const _FilterSheet({required this.currency, required this.initial}); + /// Tab yang sedang aktif; hanya tipe milik mata uang ini yang ditawarkan. + final WalletCurrency currency; final _HistoryFilter initial; @override @@ -273,6 +276,8 @@ class _FilterSheet extends StatefulWidget { } class _FilterSheetState extends State<_FilterSheet> { + late final List _filterable = + WalletTransactionType.filterableFor(widget.currency); late final Set _types = {...widget.initial.types}; late DateTime? _from = widget.initial.from; late DateTime? _to = widget.initial.to; @@ -316,7 +321,7 @@ class _FilterSheetState extends State<_FilterSheet> { Wrap( spacing: 8, runSpacing: 8, - children: WalletTransactionType.filterable.map((type) { + children: _filterable.map((type) { final selected = _types.contains(type); return FilterChip( label: Text(type.label, style: AppStyle.sm), @@ -361,9 +366,7 @@ class _FilterSheetState extends State<_FilterSheet> { title: 'Terapkan', onPressed: () => Navigator.of(context).pop( _HistoryFilter( - types: WalletTransactionType.filterable - .where(_types.contains) - .toList(), + types: _filterable.where(_types.contains).toList(), from: _from, to: _to, ), diff --git a/lib/presentation/pages/wallet/wallet_page.dart b/lib/presentation/pages/wallet/wallet_page.dart index 38dee04..a08a255 100644 --- a/lib/presentation/pages/wallet/wallet_page.dart +++ b/lib/presentation/pages/wallet/wallet_page.dart @@ -211,11 +211,11 @@ class _WalletPageState extends State { } Widget _buildActions(WalletSummary summary) { - final actions = [ - (Icons.qr_code_2, 'Bayar di kasir', const PaymentCodeRoute()), + final actions = <(IconData, String, PageRouteInfo)>[ (Icons.swap_horiz, 'Tukar EnakCoin', const ExchangeRoute()), (Icons.send_outlined, 'Transfer', const TransferRoute()), - (Icons.casino_outlined, 'Main game', const FerrisWheelRoute()), + (Icons.casino_outlined, 'Main game', const EnakGameRoute()), + (Icons.confirmation_number_outlined, 'Voucher', const RewardRoute()), ]; return Container( diff --git a/lib/presentation/pages/wallet/widgets/wallet_transaction_tile.dart b/lib/presentation/pages/wallet/widgets/wallet_transaction_tile.dart index 2c5f537..1724395 100644 --- a/lib/presentation/pages/wallet/widgets/wallet_transaction_tile.dart +++ b/lib/presentation/pages/wallet/widgets/wallet_transaction_tile.dart @@ -22,6 +22,9 @@ class WalletTransactionTile extends StatelessWidget { Widget build(BuildContext context) { final tx = transaction; final color = signedAmountColor(tx.amount); + // Tipe baru yang belum dikenal app: description saja, tanpa label. + final isKnownType = tx.type != WalletTransactionType.unknown; + final title = isKnownType ? tx.type.label : tx.description; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), @@ -47,10 +50,10 @@ class WalletTransactionTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - tx.type.label, + title, style: AppStyle.md.copyWith(fontWeight: FontWeight.w700), ), - if (tx.description.isNotEmpty) ...[ + if (isKnownType && tx.description.isNotEmpty) ...[ const SizedBox(height: 2), Text( tx.description, diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 808da99..f0bb64a 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -47,7 +47,6 @@ class AppRouter extends RootStackRouter { AutoRoute(page: WalletRoute.page), AutoRoute(page: WalletHistoryRoute.page), AutoRoute(page: WalletExpiringRoute.page), - AutoRoute(page: PaymentCodeRoute.page), AutoRoute(page: ExchangeRoute.page), AutoRoute(page: TransferRoute.page), @@ -80,8 +79,12 @@ class AppRouter extends RootStackRouter { // Outlet AutoRoute(page: OutletRoute.page), - // Reward + // Reward (voucher EnakPoint) AutoRoute(page: RewardRoute.page), + AutoRoute(page: RewardDetailRoute.page), + + // EnakGame + AutoRoute(page: EnakGameRoute.page), // Promo AutoRoute(page: PromoRoute.page), @@ -102,7 +105,6 @@ class AppRouter extends RootStackRouter { AutoRoute(page: PaymentRoute.page), // Mini Games - AutoRoute(page: FerrisWheelRoute.page), AutoRoute(page: MisteryBoxRoute.page), // Community (halaman utamanya tab di Main) diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 0fd7d77..29b76b7 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -17,21 +17,21 @@ import 'package:enaklo/presentation/pages/account/account_my/account_my_page.dar import 'package:enaklo/presentation/pages/account/address/address_page.dart' as _i2; import 'package:enaklo/presentation/pages/account/payment/payment_page.dart' - as _i34; + as _i33; import 'package:enaklo/presentation/pages/auth/create_password/create_password_page.dart' as _i9; import 'package:enaklo/presentation/pages/auth/login/login_page.dart' as _i19; import 'package:enaklo/presentation/pages/auth/otp/otp_page.dart' as _i30; import 'package:enaklo/presentation/pages/auth/password/password_page.dart' as _i32; -import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i36; +import 'package:enaklo/presentation/pages/auth/pin/pin_page.dart' as _i35; import 'package:enaklo/presentation/pages/auth/register/register_page.dart' - as _i44; + as _i43; import 'package:enaklo/presentation/pages/coin/coin_page.dart' as _i4; import 'package:enaklo/presentation/pages/coin/pages/coin_history_page.dart' as _i3; import 'package:enaklo/presentation/pages/coin/pages/product_redeem/product_redeem_page.dart' - as _i40; + as _i39; import 'package:enaklo/presentation/pages/coming_soon/coming_soon_page.dart' as _i5; import 'package:enaklo/presentation/pages/community/community_page.dart' as _i7; @@ -50,20 +50,19 @@ import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_toda as _i14; import 'package:enaklo/presentation/pages/draw/pages/draw_detail/pages/draw_winner_page.dart' as _i15; +import 'package:enaklo/presentation/pages/enakgame/enakgame_page.dart' as _i16; import 'package:enaklo/presentation/pages/main/main_page.dart' as _i20; import 'package:enaklo/presentation/pages/main/pages/home/home_page.dart' as _i18; import 'package:enaklo/presentation/pages/main/pages/order/order_page.dart' as _i29; import 'package:enaklo/presentation/pages/main/pages/profile/profile_page.dart' - as _i41; + as _i40; import 'package:enaklo/presentation/pages/main/pages/voucher/voucher_page.dart' as _i52; import 'package:enaklo/presentation/pages/merchant/merchant_page.dart' as _i22; import 'package:enaklo/presentation/pages/merchant/pages/merchant_detail/merchant_detail_page.dart' as _i21; -import 'package:enaklo/presentation/pages/mini_games/ferris_wheel/ferris_wheel_page.dart' - as _i17; import 'package:enaklo/presentation/pages/mini_games/mistery_box/mistery_box_page.dart' as _i23; import 'package:enaklo/presentation/pages/news/news_page.dart' as _i25; @@ -76,13 +75,15 @@ import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart' import 'package:enaklo/presentation/pages/order/order_detail/order_detail_page.dart' as _i28; import 'package:enaklo/presentation/pages/outlet/outlet_page.dart' as _i31; -import 'package:enaklo/presentation/pages/pin/pin_change_page.dart' as _i35; -import 'package:enaklo/presentation/pages/pin/pin_settings_page.dart' as _i37; -import 'package:enaklo/presentation/pages/pin/pin_setup_page.dart' as _i38; -import 'package:enaklo/presentation/pages/point/point_page.dart' as _i39; +import 'package:enaklo/presentation/pages/pin/pin_change_page.dart' as _i34; +import 'package:enaklo/presentation/pages/pin/pin_settings_page.dart' as _i36; +import 'package:enaklo/presentation/pages/pin/pin_setup_page.dart' as _i37; +import 'package:enaklo/presentation/pages/point/point_page.dart' as _i38; import 'package:enaklo/presentation/pages/promo/pages/promo_detail/promo_detail_page.dart' - as _i42; -import 'package:enaklo/presentation/pages/promo/promo_page.dart' as _i43; + as _i41; +import 'package:enaklo/presentation/pages/promo/promo_page.dart' as _i42; +import 'package:enaklo/presentation/pages/reward/reward_detail_page.dart' + as _i44; import 'package:enaklo/presentation/pages/reward/reward_page.dart' as _i45; import 'package:enaklo/presentation/pages/shop/pages/shop_category/shop_category_page.dart' as _i46; @@ -92,9 +93,7 @@ import 'package:enaklo/presentation/pages/shop/shop_page.dart' as _i47; import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i49; import 'package:enaklo/presentation/pages/voucher/voucher_detail/voucher_detail_page.dart' as _i51; -import 'package:enaklo/presentation/pages/wallet/exchange_page.dart' as _i16; -import 'package:enaklo/presentation/pages/wallet/payment_code_page.dart' - as _i33; +import 'package:enaklo/presentation/pages/wallet/exchange_page.dart' as _i17; import 'package:enaklo/presentation/pages/wallet/transfer_page.dart' as _i50; import 'package:enaklo/presentation/pages/wallet/wallet_expiring_page.dart' as _i53; @@ -493,7 +492,23 @@ class DrawWinnerRoute extends _i57.PageRouteInfo { } /// generated route for -/// [_i16.ExchangePage] +/// [_i16.EnakGamePage] +class EnakGameRoute extends _i57.PageRouteInfo { + const EnakGameRoute({List<_i57.PageRouteInfo>? children}) + : super(EnakGameRoute.name, initialChildren: children); + + static const String name = 'EnakGameRoute'; + + static _i57.PageInfo page = _i57.PageInfo( + name, + builder: (data) { + return const _i16.EnakGamePage(); + }, + ); +} + +/// generated route for +/// [_i17.ExchangePage] class ExchangeRoute extends _i57.PageRouteInfo { const ExchangeRoute({List<_i57.PageRouteInfo>? children}) : super(ExchangeRoute.name, initialChildren: children); @@ -503,23 +518,7 @@ class ExchangeRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i57.WrappedRoute(child: const _i16.ExchangePage()); - }, - ); -} - -/// generated route for -/// [_i17.FerrisWheelPage] -class FerrisWheelRoute extends _i57.PageRouteInfo { - const FerrisWheelRoute({List<_i57.PageRouteInfo>? children}) - : super(FerrisWheelRoute.name, initialChildren: children); - - static const String name = 'FerrisWheelRoute'; - - static _i57.PageInfo page = _i57.PageInfo( - name, - builder: (data) { - return _i57.WrappedRoute(child: const _i17.FerrisWheelPage()); + return _i57.WrappedRoute(child: const _i17.ExchangePage()); }, ); } @@ -542,20 +541,58 @@ class HomeRoute extends _i57.PageRouteInfo { /// generated route for /// [_i19.LoginPage] -class LoginRoute extends _i57.PageRouteInfo { - const LoginRoute({List<_i57.PageRouteInfo>? children}) - : super(LoginRoute.name, initialChildren: children); +class LoginRoute extends _i57.PageRouteInfo { + LoginRoute({ + _i58.Key? key, + bool returnAfterLogin = false, + List<_i57.PageRouteInfo>? children, + }) : super( + LoginRoute.name, + args: LoginRouteArgs(key: key, returnAfterLogin: returnAfterLogin), + initialChildren: children, + ); static const String name = 'LoginRoute'; static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i57.WrappedRoute(child: const _i19.LoginPage()); + final args = data.argsAs( + orElse: () => const LoginRouteArgs(), + ); + return _i57.WrappedRoute( + child: _i19.LoginPage( + key: args.key, + returnAfterLogin: args.returnAfterLogin, + ), + ); }, ); } +class LoginRouteArgs { + const LoginRouteArgs({this.key, this.returnAfterLogin = false}); + + final _i58.Key? key; + + final bool returnAfterLogin; + + @override + String toString() { + return 'LoginRouteArgs{key: $key, returnAfterLogin: $returnAfterLogin}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! LoginRouteArgs) return false; + return key == other.key && returnAfterLogin == other.returnAfterLogin; + } + + @override + int get hashCode => key.hashCode ^ returnAfterLogin.hashCode; +} + /// generated route for /// [_i20.MainPage] class MainRoute extends _i57.PageRouteInfo { @@ -900,10 +937,15 @@ class PasswordRoute extends _i57.PageRouteInfo { PasswordRoute({ _i58.Key? key, required String phoneNumber, + bool returnAfterLogin = false, List<_i57.PageRouteInfo>? children, }) : super( PasswordRoute.name, - args: PasswordRouteArgs(key: key, phoneNumber: phoneNumber), + args: PasswordRouteArgs( + key: key, + phoneNumber: phoneNumber, + returnAfterLogin: returnAfterLogin, + ), initialChildren: children, ); @@ -914,53 +956,50 @@ class PasswordRoute extends _i57.PageRouteInfo { builder: (data) { final args = data.argsAs(); return _i57.WrappedRoute( - child: _i32.PasswordPage(key: args.key, phoneNumber: args.phoneNumber), + child: _i32.PasswordPage( + key: args.key, + phoneNumber: args.phoneNumber, + returnAfterLogin: args.returnAfterLogin, + ), ); }, ); } class PasswordRouteArgs { - const PasswordRouteArgs({this.key, required this.phoneNumber}); + const PasswordRouteArgs({ + this.key, + required this.phoneNumber, + this.returnAfterLogin = false, + }); final _i58.Key? key; final String phoneNumber; + final bool returnAfterLogin; + @override String toString() { - return 'PasswordRouteArgs{key: $key, phoneNumber: $phoneNumber}'; + return 'PasswordRouteArgs{key: $key, phoneNumber: $phoneNumber, returnAfterLogin: $returnAfterLogin}'; } @override bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! PasswordRouteArgs) return false; - return key == other.key && phoneNumber == other.phoneNumber; + return key == other.key && + phoneNumber == other.phoneNumber && + returnAfterLogin == other.returnAfterLogin; } @override - int get hashCode => key.hashCode ^ phoneNumber.hashCode; + int get hashCode => + key.hashCode ^ phoneNumber.hashCode ^ returnAfterLogin.hashCode; } /// generated route for -/// [_i33.PaymentCodePage] -class PaymentCodeRoute extends _i57.PageRouteInfo { - const PaymentCodeRoute({List<_i57.PageRouteInfo>? children}) - : super(PaymentCodeRoute.name, initialChildren: children); - - static const String name = 'PaymentCodeRoute'; - - static _i57.PageInfo page = _i57.PageInfo( - name, - builder: (data) { - return _i57.WrappedRoute(child: const _i33.PaymentCodePage()); - }, - ); -} - -/// generated route for -/// [_i34.PaymentPage] +/// [_i33.PaymentPage] class PaymentRoute extends _i57.PageRouteInfo { const PaymentRoute({List<_i57.PageRouteInfo>? children}) : super(PaymentRoute.name, initialChildren: children); @@ -970,13 +1009,13 @@ class PaymentRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i34.PaymentPage(); + return const _i33.PaymentPage(); }, ); } /// generated route for -/// [_i35.PinChangePage] +/// [_i34.PinChangePage] class PinChangeRoute extends _i57.PageRouteInfo { const PinChangeRoute({List<_i57.PageRouteInfo>? children}) : super(PinChangeRoute.name, initialChildren: children); @@ -986,13 +1025,13 @@ class PinChangeRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i57.WrappedRoute(child: const _i35.PinChangePage()); + return _i57.WrappedRoute(child: const _i34.PinChangePage()); }, ); } /// generated route for -/// [_i36.PinPage] +/// [_i35.PinPage] class PinRoute extends _i57.PageRouteInfo { PinRoute({ _i58.Key? key, @@ -1013,7 +1052,7 @@ class PinRoute extends _i57.PageRouteInfo { final args = data.argsAs( orElse: () => const PinRouteArgs(), ); - return _i36.PinPage( + return _i35.PinPage( key: args.key, isCreatePin: args.isCreatePin, title: args.title, @@ -1050,7 +1089,7 @@ class PinRouteArgs { } /// generated route for -/// [_i37.PinSettingsPage] +/// [_i36.PinSettingsPage] class PinSettingsRoute extends _i57.PageRouteInfo { const PinSettingsRoute({List<_i57.PageRouteInfo>? children}) : super(PinSettingsRoute.name, initialChildren: children); @@ -1060,13 +1099,13 @@ class PinSettingsRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i37.PinSettingsPage(); + return const _i36.PinSettingsPage(); }, ); } /// generated route for -/// [_i38.PinSetupPage] +/// [_i37.PinSetupPage] class PinSetupRoute extends _i57.PageRouteInfo { PinSetupRoute({ _i58.Key? key, @@ -1085,7 +1124,7 @@ class PinSetupRoute extends _i57.PageRouteInfo { builder: (data) { final args = data.argsAs(); return _i57.WrappedRoute( - child: _i38.PinSetupPage(key: args.key, purpose: args.purpose), + child: _i37.PinSetupPage(key: args.key, purpose: args.purpose), ); }, ); @@ -1115,7 +1154,7 @@ class PinSetupRouteArgs { } /// generated route for -/// [_i39.PointPage] +/// [_i38.PointPage] class PointRoute extends _i57.PageRouteInfo { const PointRoute({List<_i57.PageRouteInfo>? children}) : super(PointRoute.name, initialChildren: children); @@ -1125,13 +1164,13 @@ class PointRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i39.PointPage(); + return const _i38.PointPage(); }, ); } /// generated route for -/// [_i40.ProductRedeemPage] +/// [_i39.ProductRedeemPage] class ProductRedeemRoute extends _i57.PageRouteInfo { ProductRedeemRoute({ _i58.Key? key, @@ -1154,7 +1193,7 @@ class ProductRedeemRoute extends _i57.PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return _i40.ProductRedeemPage( + return _i39.ProductRedeemPage( key: args.key, product: args.product, pointCard: args.pointCard, @@ -1195,7 +1234,7 @@ class ProductRedeemRouteArgs { } /// generated route for -/// [_i41.ProfilePage] +/// [_i40.ProfilePage] class ProfileRoute extends _i57.PageRouteInfo { const ProfileRoute({List<_i57.PageRouteInfo>? children}) : super(ProfileRoute.name, initialChildren: children); @@ -1205,13 +1244,13 @@ class ProfileRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return _i57.WrappedRoute(child: const _i41.ProfilePage()); + return _i57.WrappedRoute(child: const _i40.ProfilePage()); }, ); } /// generated route for -/// [_i42.PromoDetailPage] +/// [_i41.PromoDetailPage] class PromoDetailRoute extends _i57.PageRouteInfo { PromoDetailRoute({ _i58.Key? key, @@ -1229,7 +1268,7 @@ class PromoDetailRoute extends _i57.PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return _i42.PromoDetailPage(key: args.key, promo: args.promo); + return _i41.PromoDetailPage(key: args.key, promo: args.promo); }, ); } @@ -1258,7 +1297,7 @@ class PromoDetailRouteArgs { } /// generated route for -/// [_i43.PromoPage] +/// [_i42.PromoPage] class PromoRoute extends _i57.PageRouteInfo { const PromoRoute({List<_i57.PageRouteInfo>? children}) : super(PromoRoute.name, initialChildren: children); @@ -1268,13 +1307,13 @@ class PromoRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i43.PromoPage(); + return const _i42.PromoPage(); }, ); } /// generated route for -/// [_i44.RegisterPage] +/// [_i43.RegisterPage] class RegisterRoute extends _i57.PageRouteInfo { RegisterRoute({ _i58.Key? key, @@ -1293,7 +1332,7 @@ class RegisterRoute extends _i57.PageRouteInfo { builder: (data) { final args = data.argsAs(); return _i57.WrappedRoute( - child: _i44.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), + child: _i43.RegisterPage(key: args.key, phoneNumber: args.phoneNumber), ); }, ); @@ -1322,6 +1361,55 @@ class RegisterRouteArgs { int get hashCode => key.hashCode ^ phoneNumber.hashCode; } +/// generated route for +/// [_i44.RewardDetailPage] +class RewardDetailRoute extends _i57.PageRouteInfo { + RewardDetailRoute({ + _i58.Key? key, + required _i62.Voucher voucher, + List<_i57.PageRouteInfo>? children, + }) : super( + RewardDetailRoute.name, + args: RewardDetailRouteArgs(key: key, voucher: voucher), + initialChildren: children, + ); + + static const String name = 'RewardDetailRoute'; + + static _i57.PageInfo page = _i57.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i57.WrappedRoute( + child: _i44.RewardDetailPage(key: args.key, voucher: args.voucher), + ); + }, + ); +} + +class RewardDetailRouteArgs { + const RewardDetailRouteArgs({this.key, required this.voucher}); + + final _i58.Key? key; + + final _i62.Voucher voucher; + + @override + String toString() { + return 'RewardDetailRouteArgs{key: $key, voucher: $voucher}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! RewardDetailRouteArgs) return false; + return key == other.key && voucher == other.voucher; + } + + @override + int get hashCode => key.hashCode ^ voucher.hashCode; +} + /// generated route for /// [_i45.RewardPage] class RewardRoute extends _i57.PageRouteInfo { @@ -1333,7 +1421,7 @@ class RewardRoute extends _i57.PageRouteInfo { static _i57.PageInfo page = _i57.PageInfo( name, builder: (data) { - return const _i45.RewardPage(); + return _i57.WrappedRoute(child: const _i45.RewardPage()); }, ); } diff --git a/lib/sample/news_sample_data.dart b/lib/sample/news_sample_data.dart index a744cc0..1005f9d 100644 --- a/lib/sample/news_sample_data.dart +++ b/lib/sample/news_sample_data.dart @@ -137,18 +137,20 @@ List get newsSamples => [ ), NewsArticle( id: 'n6', - title: 'Cara Bayar Pakai EnakPoint di Kasir', - summary: 'Cukup tunjukkan kode bayar. PIN tidak pernah diketik di kasir.', + title: 'Cara Tukar EnakPoint ke Voucher', + summary: + 'Pilih voucher di EnakReward, masukkan PIN, lalu tunjukkan ke kasir.', category: NewsCategory.tips, publishedAt: _daysAgo(5), readMinutes: 3, paragraphs: [ - 'EnakPoint bisa langsung dipakai untuk membayar di kasir. Setiap 1 ' - 'EnakPoint setara potongan Rp 1.', - 'Tekan tombol Bayar di tengah menu bawah, lalu masukkan PIN kamu. ' - 'Aplikasi akan menampilkan kode angka dan QR.', - 'Tunjukkan kode itu ke kasir. Kasir yang memilih berapa EnakPoint yang ' - 'dipakai. Kode berlaku 2 menit dan hanya bisa dipakai sekali.', + 'EnakPoint kamu bisa ditukar dengan voucher, misalnya menu gratis atau ' + 'potongan belanja. Buka menu EnakReward untuk melihat voucher yang ' + 'tersedia.', + 'Pilih voucher, tekan Tukar, lalu masukkan PIN kamu. Penukaran tidak ' + 'bisa dibatalkan.', + 'Voucher yang sudah ditukar ada di tab Voucher saya. Tunjukkan layar ' + 'voucher atau kodenya ke kasir saat memesan.', 'PIN hanya dimasukkan di aplikasi, tidak pernah di mesin kasir. Jangan ' 'berikan PIN kamu kepada siapa pun, termasuk petugas outlet.', ], diff --git a/lib/sample/reward_sample_data.dart b/lib/sample/reward_sample_data.dart deleted file mode 100644 index 6888443..0000000 --- a/lib/sample/reward_sample_data.dart +++ /dev/null @@ -1,139 +0,0 @@ -// Models -enum RewardStatus { active, used, expired } - -class Reward { - final String id; - final String name; - final String emoji; - final String description; - final String categoryName; - final int pointsUsed; - final DateTime redeemedAt; - final DateTime validUntil; - final RewardStatus status; - final String? couponCode; - final DateTime? usedAt; - final String? termsAndConditions; - - const Reward({ - required this.id, - required this.name, - required this.emoji, - required this.description, - required this.categoryName, - required this.pointsUsed, - required this.redeemedAt, - required this.validUntil, - required this.status, - this.couponCode, - this.usedAt, - this.termsAndConditions, - }); -} - -// Sample Data -// -// 1 EnakPoint = potongan Rp 1, jadi harga reward dibuat sedikit di bawah harga -// menunya supaya menukar lebih untung daripada bayar langsung pakai poin. -// Menu gratis berlaku 14 hari dan voucher potongan 30 hari sejak ditukar. -DateTime _daysAgo(int days) => DateTime.now().subtract(Duration(days: days)); -DateTime _daysFromNow(int days) => DateTime.now().add(Duration(days: days)); - -/// Getter, bukan `final`, supaya perubahan data langsung terbaca saat hot -/// reload (variabel global yang sudah terisi tidak diisi ulang). -List get rewardSamples => [ - Reward( - id: 'r1', - name: 'Kopi Susu', - emoji: '☕', - description: '1 Kopi Susu regular di Tumulu Coffee', - categoryName: 'Minuman', - pointsUsed: 20000, - redeemedAt: _daysAgo(11), - validUntil: _daysFromNow(3), - status: RewardStatus.active, - couponCode: 'ENK-7QM2PX', - termsAndConditions: - 'Berlaku untuk 1 Kopi Susu ukuran regular di Tumulu Coffee, dine-in ' - 'atau take away. Upsize dan tambahan topping dibayar terpisah. ' - 'Tunjukkan kode ke kasir sebelum membayar.', - ), - Reward( - id: 'r2', - name: 'Bakso Urat', - emoji: '🍲', - description: '1 porsi Bakso Urat di Bakso 343', - categoryName: 'Makanan', - pointsUsed: 18000, - redeemedAt: DateTime.now().subtract(const Duration(hours: 5)), - validUntil: _daysFromNow(14), - status: RewardStatus.active, - couponCode: 'ENK-B3HW8N', - termsAndConditions: - 'Berlaku untuk 1 porsi Bakso Urat di Bakso 343, dine-in atau take ' - 'away. Tunjukkan kode ke kasir sebelum membayar.', - ), - Reward( - id: 'r3', - name: 'Potongan Rp 10.000', - emoji: '🎟️', - description: 'Min. belanja Rp 50.000 di semua outlet', - categoryName: 'Voucher', - pointsUsed: 8000, - redeemedAt: _daysAgo(2), - validUntil: _daysFromNow(28), - status: RewardStatus.active, - couponCode: 'ENK-K9D4TR', - termsAndConditions: - 'Berlaku untuk 1 transaksi dengan minimum belanja Rp 50.000 di semua ' - 'outlet. Tidak dapat digabung dengan voucher lain dan tidak dapat ' - 'diuangkan.', - ), - Reward( - id: 'r4', - name: 'Es Teh Manis', - emoji: '🥤', - description: '1 gelas Es Teh Manis di semua outlet', - categoryName: 'Minuman', - pointsUsed: 4000, - redeemedAt: _daysAgo(9), - validUntil: _daysFromNow(5), - usedAt: _daysAgo(8), - status: RewardStatus.used, - couponCode: 'ENK-5TRJ6C', - termsAndConditions: - 'Berlaku untuk 1 gelas Es Teh Manis ukuran regular di semua outlet.', - ), - Reward( - id: 'r5', - name: 'Gyoza Goreng', - emoji: '🥟', - description: '5 pcs Gyoza goreng di Ramenmulu', - categoryName: 'Cemilan', - pointsUsed: 16000, - redeemedAt: _daysAgo(20), - validUntil: _daysAgo(6), - usedAt: _daysAgo(16), - status: RewardStatus.used, - couponCode: 'ENK-G8VL2F', - termsAndConditions: - 'Berlaku untuk 1 porsi Gyoza goreng isi 5 di Ramenmulu. Tidak berlaku ' - 'untuk menu paket.', - ), - Reward( - id: 'r6', - name: 'Potongan Rp 5.000', - emoji: '🎟️', - description: 'Min. belanja Rp 30.000 di semua outlet', - categoryName: 'Voucher', - pointsUsed: 4000, - redeemedAt: _daysAgo(50), - validUntil: _daysAgo(20), - status: RewardStatus.expired, - couponCode: 'ENK-P2XN7D', - termsAndConditions: - 'Berlaku untuk 1 transaksi dengan minimum belanja Rp 30.000 di semua ' - 'outlet. Tidak dapat digabung dengan voucher lain dan tidak dapat ' - 'diuangkan.', - ), -]; diff --git a/test/customer_integration_test.dart b/test/customer_integration_test.dart new file mode 100644 index 0000000..7e01179 --- /dev/null +++ b/test/customer_integration_test.dart @@ -0,0 +1,187 @@ +import 'package:dio/dio.dart'; +import 'package:enaklo/common/api/interceptors/unauthorized_interceptor.dart'; +import 'package:enaklo/domain/wallet/wallet.dart'; +import 'package:enaklo/infrastructure/wallet/wallet_dtos.dart'; +import 'package:enaklo/infrastructure/wallet/wallet_response.dart'; +import 'package:enaklo/presentation/pages/reward/reward_ui.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Response _response(int status, Object? data) => Response( + requestOptions: RequestOptions(path: '/'), + statusCode: status, + data: data, +); + +Map _error(String code, String entity, String cause) => { + 'success': false, + 'data': null, + 'errors': [ + {'code': code, 'entity': entity, 'cause': cause}, + ], +}; + +void main() { + group('token ditolak (§2.2)', () { + test('401 dan 304 dari auth_handler berarti login ulang', () { + expect(isTokenRejected(_response(401, null)), isTrue); + expect( + isTokenRejected(_response(400, _error('304', 'auth_handler', 'x'))), + isTrue, + ); + }); + + test('304 aturan bisnis dan 403 PIN tidak dianggap logout', () { + expect( + isTokenRejected( + _response(400, _error('304', 'wallet_service', 'not enough')), + ), + isFalse, + ); + expect( + isTokenRejected( + _response(403, { + 'success': false, + 'data': {'code': 'PIN_NOT_SET'}, + 'errors': [ + {'code': 'PIN_NOT_SET'}, + ], + }), + ), + isFalse, + ); + }); + + test('parseWalletFailure memetakan auth_handler ke unauthorized', () { + final failure = parseWalletFailure( + _response(400, _error('304', 'auth_handler', 'token expired')), + ); + expect(failure, const WalletFailure.unauthorized()); + }); + + test('parseWalletFailure tetap membaca error PIN dari data', () { + final failure = parseWalletFailure( + _response(400, { + 'success': false, + 'data': {'code': 'PIN_INVALID', 'remaining_attempts': 3}, + 'errors': [ + {'code': 'PIN_INVALID'}, + ], + }), + ); + expect(failure, const WalletFailure.pinInvalid(3)); + }); + }); + + group('tipe mutasi (§4.2)', () { + test('tipe game dan voucher dikenali, PAYMENT tidak lagi', () { + expect( + WalletTransactionType.fromApi('GAME_REWARD'), + WalletTransactionType.gameReward, + ); + expect( + WalletTransactionType.fromApi('REWARD_REDEEM_REFUND'), + WalletTransactionType.rewardRedeemRefund, + ); + expect( + WalletTransactionType.fromApi('PAYMENT'), + WalletTransactionType.unknown, + ); + }); + + test('filter per tab hanya menawarkan tipe milik mata uang itu', () { + final point = WalletTransactionType.filterableFor(WalletCurrency.point); + final coin = WalletTransactionType.filterableFor(WalletCurrency.coin); + + expect(point, contains(WalletTransactionType.rewardRedeem)); + expect(point, isNot(contains(WalletTransactionType.gameSpend))); + expect(coin, contains(WalletTransactionType.gameSpendRefund)); + expect(coin, isNot(contains(WalletTransactionType.exchangeIn))); + expect(point, isNot(contains(WalletTransactionType.unknown))); + }); + }); + + group('penolakan tukar voucher (§9.2)', () { + ({String message, bool reloadCatalog}) describe(String cause) => + describeVoucherRedeemFailure(WalletFailure.rejected(cause)); + + test('setiap cause punya pesan ramah', () { + expect( + describe('not enough EnakPoint').message, + 'EnakPoint kamu kurang.', + ); + expect( + describe( + 'this voucher can be redeemed at most 2 times per customer', + ).message, + 'Kamu sudah mencapai batas penukaran voucher ini.', + ); + expect( + describe('the customer is not active').message, + 'Akun tidak aktif.', + ); + }); + + test('voucher habis / tidak tersedia memuat ulang katalog', () { + for (final cause in [ + 'the voucher is out of stock', + 'the voucher is not available', + 'the voucher cannot be redeemed yet', + 'the voucher has ended', + 'the voucher is not available yet', + ]) { + expect(describe(cause).reloadCatalog, isTrue, reason: cause); + } + expect(describe('not enough EnakPoint').reloadCatalog, isFalse); + }); + + test('cause tidak pernah ditampilkan mentah', () { + const cause = + 'this Idempotency-Key was already used to redeem another voucher'; + expect(describe(cause).message, isNot(contains('Idempotency'))); + }); + }); + + group('DTO dari contoh dokumen', () { + test( + 'voucher katalog (§9.1): stok null tidak dihitung, 0 berarti habis', + () { + Voucher voucher(int? available) => VoucherDto.fromJson({ + 'id': 'v1', + 'name': 'Kopi Susu Gratis', + 'voucher_type': 'FREE_ITEM', + 'face_value': 20000, + 'point_cost': 15000, + 'max_per_customer': null, + 'valid_until': '2026-12-31T16:59:59Z', + 'terms': {'a': 'b'}, + 'available': available, + }).toDomain(); + + expect(voucher(null).isOutOfStock, isFalse); + expect(voucher(0).isOutOfStock, isTrue); + expect(voucher(120).type, VoucherType.freeItem); + }, + ); + + test('hasil tukar voucher (§9.2)', () { + final result = VoucherRedemptionDto.fromJson({ + 'id': 'r1', + 'voucher_id': 'v1', + 'voucher_name': 'Kopi Susu Gratis', + 'voucher_type': 'FREE_ITEM', + 'status': 'COMPLETED', + 'face_value': 20000, + 'point_cost': 15000, + 'code': 'KOPI-7F3C-2291', + 'code_expires_at': '2026-12-31T16:59:59Z', + 'point_balance': 2500, + 'replayed': true, + }).toRedeemResult(); + + expect(result.pointBalance, 2500); + expect(result.replayed, isTrue); + expect(result.redemption.code, 'KOPI-7F3C-2291'); + expect(result.redemption.status, VoucherRedemptionStatus.completed); + }); + }); +}