Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a87cb9b15 | ||
|
|
f66f706b65 | ||
|
|
6d05385e66 | ||
|
|
4ee16d3855 | ||
|
|
84cae9e789 | ||
|
|
38850b281f | ||
|
|
e5515d5871 | ||
|
|
cb5e5e70bf | ||
|
|
671dd3a5d0 | ||
|
|
66d59ee59e | ||
|
|
57fb56303a | ||
|
|
8f0a5d3fb5 | ||
|
|
5a8b020256 | ||
|
|
e273785268 | ||
|
|
5ba60747a8 | ||
|
|
c35d0eedbe | ||
|
|
bc751246d8 | ||
|
|
f5e3075205 | ||
|
|
5954510b93 |
@@ -0,0 +1,47 @@
|
||||
name: Build Android APK (split per ABI)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-apk:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
cache: "gradle"
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
# Samain dengan versi di ios.yml biar konsisten.
|
||||
flutter-version: "3.47.0"
|
||||
cache: true
|
||||
|
||||
- name: Install Dependencies
|
||||
run: flutter pub get
|
||||
|
||||
# ── Build ───────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Build APK (split per ABI)
|
||||
run: flutter build apk --release --split-per-abi
|
||||
|
||||
# ── Artifact ────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Upload APKs as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: android-apk
|
||||
path: build/app/outputs/flutter-apk/app-*-release.apk
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,150 @@
|
||||
name: Build & Deploy iOS to TestFlight
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: macos-26
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
# Dipin biar CI nggak ikut naik versi diam-diam.
|
||||
# Flutter 3.47 mensyaratkan iOS deployment target minimal 15.0.
|
||||
flutter-version: "3.47.0"
|
||||
cache: true
|
||||
|
||||
- name: Install Dependencies
|
||||
run: flutter pub get
|
||||
|
||||
# ── Code Signing Setup ──────────────────────────────────────────────────
|
||||
|
||||
- name: Install Apple Certificate
|
||||
env:
|
||||
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
|
||||
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
|
||||
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
|
||||
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
|
||||
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A \
|
||||
-t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
security set-key-partition-list -S apple-tool:,apple: \
|
||||
-s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
|
||||
- name: Install Provisioning Profile
|
||||
env:
|
||||
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
|
||||
run: |
|
||||
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
|
||||
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
|
||||
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
# Xcode requires the filename to be the profile's UUID
|
||||
UUID=$(security cms -D -i $PP_PATH | plutil -extract UUID raw -)
|
||||
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$UUID.mobileprovision
|
||||
echo "Installed provisioning profile UUID: $UUID"
|
||||
|
||||
# ── Disable SPM ─────────────────────────────────────────────────────────
|
||||
|
||||
- name: Disable Swift Package Manager
|
||||
run: flutter config --no-enable-swift-package-manager
|
||||
|
||||
# ── CocoaPods ───────────────────────────────────────────────────────────
|
||||
|
||||
- name: Install CocoaPods Dependencies
|
||||
run: |
|
||||
cd ios
|
||||
rm -rf Pods Podfile.lock
|
||||
pod install --repo-update
|
||||
|
||||
# ── Build & Archive ─────────────────────────────────────────────────────
|
||||
|
||||
- name: Build iOS (no codesign)
|
||||
run: flutter build ios --release --no-codesign
|
||||
|
||||
- name: Re-run pod install to apply Podfile signing settings
|
||||
run: |
|
||||
cd ios
|
||||
pod install
|
||||
|
||||
- name: Archive with Xcode
|
||||
env:
|
||||
TEAM_ID: ${{ secrets.TEAM_ID }}
|
||||
run: |
|
||||
cat > $RUNNER_TEMP/signing.xcconfig << 'XCCONFIG'
|
||||
CODE_SIGN_STYLE = Manual
|
||||
CODE_SIGN_IDENTITY = iPhone Distribution
|
||||
PROVISIONING_PROFILE_SPECIFIER = Enaklo Customer
|
||||
CODE_SIGNING_REQUIRED = YES
|
||||
XCCONFIG
|
||||
|
||||
xcodebuild archive \
|
||||
-workspace ios/Runner.xcworkspace \
|
||||
-scheme Runner \
|
||||
-configuration Release \
|
||||
-archivePath $RUNNER_TEMP/Runner.xcarchive \
|
||||
-destination "generic/platform=iOS" \
|
||||
-xcconfig $RUNNER_TEMP/signing.xcconfig \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID"
|
||||
|
||||
- name: Export IPA
|
||||
run: |
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath $RUNNER_TEMP/Runner.xcarchive \
|
||||
-exportPath $RUNNER_TEMP/export \
|
||||
-exportOptionsPlist ios/ExportOptions.plist
|
||||
|
||||
mkdir -p build/ios/ipa
|
||||
cp $RUNNER_TEMP/export/*.ipa build/ios/ipa/
|
||||
|
||||
# ── Upload to TestFlight ────────────────────────────────────────────────
|
||||
|
||||
- name: Upload to TestFlight via Transporter
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
|
||||
run: |
|
||||
IPA_PATH=$(find build/ios/ipa -name "*.ipa" | head -1)
|
||||
echo "Uploading: $IPA_PATH"
|
||||
xcrun altool --upload-app \
|
||||
--type ios \
|
||||
--file "$IPA_PATH" \
|
||||
--username "$APPLE_ID" \
|
||||
--password "$APP_SPECIFIC_PASSWORD" \
|
||||
--verbose
|
||||
|
||||
# ── Cleanup ─────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Clean Up Keychain and Provisioning Profile
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
|
||||
rm -rf ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision || true
|
||||
|
||||
# ── Artifact ────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Upload IPA as Artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios-ipa
|
||||
path: build/ios/ipa/*.ipa
|
||||
retention-days: 7
|
||||
@@ -43,3 +43,10 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Signing credentials — never commit these
|
||||
*.p12
|
||||
*.pfx
|
||||
*.cer
|
||||
*.mobileprovision
|
||||
*.certSigningRequest
|
||||
|
||||
@@ -18,3 +18,10 @@ analyzer:
|
||||
- test/generated/**
|
||||
- "**/**.g.dart"
|
||||
- "**/**.freezed.dart"
|
||||
- build/**
|
||||
- android/**
|
||||
- ios/**
|
||||
- web/**
|
||||
- windows/**
|
||||
- macos/**
|
||||
- linux/**
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.appskel.enaklo"
|
||||
namespace = "com.apskel.enaklocustomer"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
@@ -21,7 +21,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.appskel.enaklo"
|
||||
applicationId = "com.apskel.enaklocustomer"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
|
||||
|
||||
<application
|
||||
android:label="Enaklo"
|
||||
android:label="Enaklo Club"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.apskel.enaklocustomer
|
||||
|
||||
import android.os.Build
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
// Pengaturan WebView yang tidak disediakan plugin webview_flutter.
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, WEBVIEW_CHANNEL)
|
||||
.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"enableOffscreenPreRaster" -> {
|
||||
val id = call.argument<Number>("webViewId")?.toLong()
|
||||
@Suppress("DEPRECATION")
|
||||
val webView = id?.let {
|
||||
WebViewFlutterAndroidExternalApi.getWebView(flutterEngine, it)
|
||||
}
|
||||
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
webView.settings.offscreenPreRaster = true
|
||||
}
|
||||
result.success(webView != null)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val WEBVIEW_CHANNEL = "com.apskel.enaklocustomer/webview"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.appskel.enaklo
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 50 KiB |
@@ -1,3 +1,7 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||
android.builtInKotlin=false
|
||||
# This newDsl flag was added automatically by Flutter migrator
|
||||
android.newDsl=false
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
|
||||
@@ -18,8 +18,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.7.3" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 790 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 851 KiB After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 851 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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 <token login customer>`
|
||||
- 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": "<id unik & stabil per instalasi>", "fcm_token": "<token FCM>", "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('<json>')` di webview.
|
||||
|
||||
| Pesan masuk dari game | Yang dilakukan aplikasi |
|
||||
|---|---|
|
||||
| `{ "type": "ready" }` | Kirim `{ "type": "init", "api_base_url": "<base URL>/api/v1", "token": "<token customer>", "game_id": "<id game yang dibuka>" }` |
|
||||
| `{ "type": "token_expired" }` | Login ulang customer (tidak ada refresh token), lalu kirim `{ "type": "token", "token": "<token baru>" }` |
|
||||
| `{ "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.
|
||||
@@ -0,0 +1,617 @@
|
||||
# Prompt: fitur EnakPoint & EnakCoin di Mobile App Customer
|
||||
|
||||
Kamu mengerjakan aplikasi mobile untuk **customer** (bukan kasir, bukan backoffice).
|
||||
Tugasmu: membangun fitur loyalitas EnakPoint & EnakCoin di aplikasi, 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.
|
||||
|
||||
---
|
||||
|
||||
## 1. Konteks bisnis
|
||||
|
||||
| | EnakPoint (`POINT`) | EnakCoin (`COIN`) |
|
||||
| ----------------------------- | ---------------------------------------------------- | ----------------------------------- |
|
||||
| Didapat dari | Belanja (order lunas), koreksi admin, tukar EnakCoin | Belanja, koreksi admin |
|
||||
| Dipakai untuk | **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, dan endpoint serta
|
||||
field bernama token sudah dihapus dari API.
|
||||
|
||||
### 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 fitur tarik tunai.
|
||||
3. **PIN 6 digit wajib** untuk: membuat kode bayar, tukar
|
||||
EnakCoin, dan transfer. **Main game tidak butuh PIN.** 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.
|
||||
|
||||
---
|
||||
|
||||
## 2. Koneksi ke API
|
||||
|
||||
- Base URL: `/api/v1`
|
||||
- Semua endpoint customer: header `Authorization: Bearer <token login customer>`
|
||||
- Semua jumlah di request dan response berupa integer.
|
||||
|
||||
### 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.
|
||||
|
||||
### Format response
|
||||
|
||||
Sukses:
|
||||
|
||||
```json
|
||||
{ "success": true, "data": { … }, "errors": null }
|
||||
```
|
||||
|
||||
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 | Tampilkan pesan yang ramah (lihat tiap fitur); `cause` berbahasa Inggris, jangan tampilkan mentah |
|
||||
| `404` | 404 | Tidak ditemukan | Tampilkan "tidak ditemukan" |
|
||||
| `429` | 429 | Minta OTP terlalu cepat | Tampilkan 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" |
|
||||
|
||||
### Idempotency-Key
|
||||
|
||||
Endpoint **tukar** dan **transfer** wajib header `Idempotency-Key` (string unik, maks.
|
||||
50 karakter, mis. UUID v4).
|
||||
|
||||
- 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` | – |
|
||||
| Kode bayar (angka + QR) | `POST /customer/wallet/payment-code` | Ya |
|
||||
| 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/*` | – |
|
||||
| Game | `POST /customer/spin` | – |
|
||||
| (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: Bayar di kasir (§7.1), Tukar EnakCoin (§8.1), Transfer (§8.2), Main game (§9).
|
||||
|
||||
Muat ulang beranda setelah setiap transaksi 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,PAYMENT` | 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 `−`.
|
||||
- `description` sudah siap tampil (nama lawan transfer sudah disamarkan). 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` | Label | Arah |
|
||||
| ---------------- | --------------------------------- | ----- |
|
||||
| `EARN` | Dari belanja | + |
|
||||
| `EARN_REVERSAL` | Dibatalkan (order di-void/refund) | − |
|
||||
| `PAYMENT` | Bayar pesanan | − |
|
||||
| `PAYMENT_REFUND` | Pengembalian pembayaran | + |
|
||||
| `EXCHANGE_OUT` | Ditukar ke EnakPoint | − |
|
||||
| `EXCHANGE_IN` | Hasil tukar EnakCoin | + |
|
||||
| `TRANSFER_OUT` | Transfer keluar | − |
|
||||
| `TRANSFER_IN` | Transfer masuk | + |
|
||||
| `GAME_SPEND` | Main game | − |
|
||||
| `EXPIRE` | Kedaluwarsa | − |
|
||||
| `ADJUSTMENT` | Koreksi | + / − |
|
||||
| `MIGRATION` | Saldo awal | + |
|
||||
|
||||
### 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",
|
||||
"accepts_point_payment": true,
|
||||
"earns_points": true,
|
||||
"earns_coins": false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- `address` bisa `null`.
|
||||
- `accepts_point_payment`: kasir di outlet ini menerima pembayaran EnakPoint. Pakai
|
||||
untuk label "Bisa bayar pakai EnakPoint".
|
||||
- `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": "EnakPoint", "method_type": "point", "amount": 12500, "status": "completed", "refund_amount": 0, "points_used": 12500, "point_value": 1, "created_at": "…" },
|
||||
{ "id": "…", "method_name": "Cash", "method_type": "cash", "amount": 86500, "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".
|
||||
- Pembayaran EnakPoint membawa `points_used`; tampilkan "EnakPoint 12.500 (Rp 12.500)".
|
||||
- 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": "<id unik & stabil per instalasi>", "fcm_token": "<token FCM>", "platform": "android", "app_version": "2.4.0" }
|
||||
```
|
||||
|
||||
- `device_id` wajib, stabil untuk satu instalasi (simpan di secure storage).
|
||||
- `platform`: `android`, `ios`, atau `web`.
|
||||
- Saat **logout**, panggil `DELETE /api/v1/customer/devices/{device_id}` sebelum
|
||||
menghapus token login, supaya HP itu tidak lagi menerima notifikasi akun ini.
|
||||
|
||||
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` | Beberapa 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. 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**;
|
||||
bayar dan tukar 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. Membayar dengan EnakPoint
|
||||
|
||||
App customer tidak membuat atau membayar order; order hanya bisa dilihat (§4.5).
|
||||
EnakPoint hanya dipakai membayar di kasir, lewat kode bayar dari app. Jangan membangun
|
||||
layar checkout atau memanggil `POST /customer/orders/:id/pay-with-points`.
|
||||
|
||||
### 7.1 Di kasir — kode bayar
|
||||
|
||||
Customer tidak pernah mengetik PIN di mesin kasir. Alurnya:
|
||||
|
||||
1. Customer membuka "Bayar di kasir" dan memasukkan PIN.
|
||||
2. `POST /api/v1/customer/wallet/payment-code` dengan `{ "pin": "482913" }`:
|
||||
|
||||
```json
|
||||
{ "code": "482913", "qr_payload": "enakpoint:482913", "expires_at": "2026-09-30T05:02:00Z" }
|
||||
```
|
||||
|
||||
3. Tampilkan `code` besar (angka) **dan** QR dari `qr_payload` (string apa adanya).
|
||||
4. Tampilkan hitung mundur ke `expires_at` (2 menit). Setelah habis, sembunyikan kode
|
||||
dan tampilkan tombol "Buat kode baru".
|
||||
5. Kasir memindai/mengetik kode dan memilih jumlah EnakPoint. App tidak menerima
|
||||
callback; setelah customer kembali ke beranda, muat ulang saldo.
|
||||
|
||||
Kode sekali pakai. Membuat kode baru membatalkan kode lama.
|
||||
|
||||
### 7.2 Refund
|
||||
|
||||
Bila order yang dibayar EnakPoint dibatalkan atau direfund, EnakPoint kembali sebagai
|
||||
EnakPoint (tidak pernah tunai) dan muncul di riwayat sebagai `PAYMENT_REFUND`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Tukar dan transfer
|
||||
|
||||
### 8.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}".
|
||||
|
||||
### 8.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`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Game (memakai EnakCoin)
|
||||
|
||||
`POST /api/v1/customer/spin` dengan `{ "spin_id": "<id game>" }`. Tanpa PIN.
|
||||
|
||||
```json
|
||||
{
|
||||
"game_play": { "id": "…", "game_id": "…", "coins_used": 1, "created_at": "…" },
|
||||
"prize_won": { "id": "…", "name": "Voucher 10rb" },
|
||||
"coins_remaining": 7
|
||||
}
|
||||
```
|
||||
|
||||
- Setiap game punya biaya sendiri: `metadata.coin_cost` pada data game dari
|
||||
`GET /api/v1/customer/games` (atau `GET /customer/ferris-wheel`), default 1 bila kosong.
|
||||
Tampilkan biaya sebelum main, dan nonaktifkan tombol bila `coin_balance` kurang.
|
||||
- `304`: EnakCoin kurang, game nonaktif, atau hadiah baru saja habis. Tidak ada
|
||||
EnakCoin yang terpotong; tampilkan pesan dan biarkan customer mencoba lagi.
|
||||
- Setelah main, perbarui saldo EnakCoin dari `coins_remaining`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Yang sudah dihapus / deprecated
|
||||
|
||||
Sudah **dihapus** dari API (jangan dipanggil, akan error / tidak ada):
|
||||
|
||||
| Lama | Pengganti |
|
||||
| ------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| `GET /customer/tokens` | `GET /customer/wallet` → `coin_balance` |
|
||||
| `total_tokens`, `tokens_history` | `coin_balance`, `GET /customer/wallet/transactions?currency=COIN` |
|
||||
| `token_used`, `tokens_remaining` di response game | `coins_used`, `coins_remaining` |
|
||||
|
||||
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, label tipe sesuai §4.2.
|
||||
- [ ] 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.
|
||||
- [ ] Kode bayar: angka + QR, hitung mundur 2 menit, tombol buat ulang.
|
||||
- [ ] Tukar dengan preview, kelipatan kurs, konfirmasi, `Idempotency-Key`, retry dengan key sama.
|
||||
- [ ] Transfer dengan cek penerima tersamar, konfirmasi, `Idempotency-Key`, retry dengan key sama.
|
||||
- [ ] Game memakai `coins_used` / `coins_remaining` dan menampilkan biaya per game.
|
||||
- [ ] Riwayat order dengan pagination dan layar detail (item, pembayaran, EnakPoint/EnakCoin yang didapat).
|
||||
- [ ] Tidak ada pemakaian endpoint atau field di §10.
|
||||
- [ ] PIN tidak pernah disimpan, di-log, atau dikirim ke analytics.
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Ganti "app-store-connect" jika perlu: ad-hoc, enterprise, development -->
|
||||
<key>method</key>
|
||||
<string>app-store-connect</string>
|
||||
|
||||
<!-- Team ID dari Apple Developer account -->
|
||||
<key>teamID</key>
|
||||
<string>5TRC3M8UZG</string>
|
||||
|
||||
<!-- Signing style: manual (pakai provisioning profile eksplisit) -->
|
||||
<key>signingStyle</key>
|
||||
<string>manual</string>
|
||||
|
||||
<!-- Provisioning profiles: key = bundle ID, value = nama profile -->
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>com.apskel.enaklocustomer</key>
|
||||
<string>Enaklo Customer</string>
|
||||
</dict>
|
||||
|
||||
<key>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
|
||||
<key>uploadBitcode</key>
|
||||
<false/>
|
||||
|
||||
<key>uploadSymbols</key>
|
||||
<true/>
|
||||
|
||||
<key>compileBitcode</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
<string>15.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
platform :ios, '15.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@@ -39,5 +39,12 @@ end
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
|
||||
# Disable code signing for all Pod targets — only Runner needs signing
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
|
||||
config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO'
|
||||
config.build_settings['CODE_SIGN_IDENTITY'] = ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -346,7 +346,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -368,7 +368,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apskel.enaklocustomer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -384,7 +384,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apskel.enaklocustomer.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -401,7 +401,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apskel.enaklocustomer.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -416,7 +416,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apskel.enaklocustomer.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -472,7 +472,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -523,7 +523,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -547,7 +547,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apskel.enaklocustomer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -569,7 +569,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.appskel.enaklo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.apskel.enaklocustomer;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
|
Before Width: | Height: | Size: 795 KiB After Width: | Height: | Size: 788 KiB |
|
Before Width: | Height: | Size: 804 B After Width: | Height: | Size: 991 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 31 KiB |
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Enaklo</string>
|
||||
<string>Enaklo Club</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
part of 'auth_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class AuthEvent with _$AuthEvent {
|
||||
abstract class AuthEvent with _$AuthEvent {
|
||||
const factory AuthEvent.fetchCurrentUser() = _FetchCurrentUser;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'auth_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class AuthState with _$AuthState {
|
||||
abstract class AuthState with _$AuthState {
|
||||
const AuthState._();
|
||||
|
||||
const factory AuthState({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'check_phone_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CheckPhoneFormEvent with _$CheckPhoneFormEvent {
|
||||
abstract class CheckPhoneFormEvent with _$CheckPhoneFormEvent {
|
||||
const factory CheckPhoneFormEvent.phoneNumberChanged(String phoneNumber) =
|
||||
_PhoneNumberChanged;
|
||||
const factory CheckPhoneFormEvent.submitted() = _Submitted;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'check_phone_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CheckPhoneFormState with _$CheckPhoneFormState {
|
||||
abstract class CheckPhoneFormState with _$CheckPhoneFormState {
|
||||
const factory CheckPhoneFormState({
|
||||
required String phoneNumber,
|
||||
required Option<Either<AuthFailure, CheckPhone>> failureOrCheckPhoneOption,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'login_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class LoginFormEvent with _$LoginFormEvent {
|
||||
abstract class LoginFormEvent with _$LoginFormEvent {
|
||||
const factory LoginFormEvent.phoneNumberChanged(String phoneNumber) =
|
||||
_PhoneNumberChanged;
|
||||
const factory LoginFormEvent.passwordChanged(String password) =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'login_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class LoginFormState with _$LoginFormState {
|
||||
abstract class LoginFormState with _$LoginFormState {
|
||||
const factory LoginFormState({
|
||||
required String phoneNumber,
|
||||
required String password,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../domain/auth/auth.dart';
|
||||
import '../../../domain/wallet/wallet.dart';
|
||||
|
||||
part 'logout_form_event.dart';
|
||||
part 'logout_form_state.dart';
|
||||
@@ -12,8 +13,10 @@ part 'logout_form_bloc.freezed.dart';
|
||||
@injectable
|
||||
class LogoutFormBloc extends Bloc<LogoutFormEvent, LogoutFormState> {
|
||||
final IAuthRepository _repository;
|
||||
final IDeviceRepository _deviceRepository;
|
||||
|
||||
LogoutFormBloc(this._repository) : super(LogoutFormState.initial()) {
|
||||
LogoutFormBloc(this._repository, this._deviceRepository)
|
||||
: super(LogoutFormState.initial()) {
|
||||
on<LogoutFormEvent>(_onLogoutFormEvent);
|
||||
}
|
||||
|
||||
@@ -24,6 +27,9 @@ class LogoutFormBloc extends Bloc<LogoutFormEvent, LogoutFormState> {
|
||||
return event.map(
|
||||
submitted: (e) async {
|
||||
emit(state.copyWith(isSubmitting: true, failureOrAuthOption: none()));
|
||||
// Hapus registrasi push sebelum token login dihapus. Kegagalannya
|
||||
// tidak menghalangi logout.
|
||||
await _deviceRepository.unregister();
|
||||
final failureOrAuth = await _repository.logout();
|
||||
emit(
|
||||
state.copyWith(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// 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 'logout_form_bloc.dart';
|
||||
@@ -9,327 +9,466 @@ part of 'logout_form_bloc.dart';
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LogoutFormEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() submitted,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? submitted,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? submitted,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Submitted value) submitted,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Submitted value)? submitted,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Submitted value)? submitted,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is LogoutFormEvent);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LogoutFormEvent()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LogoutFormEventCopyWith<$Res> {
|
||||
factory $LogoutFormEventCopyWith(
|
||||
LogoutFormEvent value,
|
||||
$Res Function(LogoutFormEvent) then,
|
||||
) = _$LogoutFormEventCopyWithImpl<$Res, LogoutFormEvent>;
|
||||
class $LogoutFormEventCopyWith<$Res> {
|
||||
$LogoutFormEventCopyWith(LogoutFormEvent _, $Res Function(LogoutFormEvent) __);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LogoutFormEventCopyWithImpl<$Res, $Val extends LogoutFormEvent>
|
||||
implements $LogoutFormEventCopyWith<$Res> {
|
||||
_$LogoutFormEventCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
/// Adds pattern-matching-related methods to [LogoutFormEvent].
|
||||
extension LogoutFormEventPatterns on LogoutFormEvent {
|
||||
/// 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();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
/// Create a copy of LogoutFormEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({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<TResult extends Object?>({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 extends Object?>({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 extends Object?>({TResult Function()? submitted,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Submitted() when submitted != null:
|
||||
return submitted();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 extends Object?>({required TResult Function() submitted,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Submitted():
|
||||
return submitted();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 extends Object?>({TResult? Function()? submitted,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Submitted() when submitted != null:
|
||||
return submitted();case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SubmittedImplCopyWith<$Res> {
|
||||
factory _$$SubmittedImplCopyWith(
|
||||
_$SubmittedImpl value,
|
||||
$Res Function(_$SubmittedImpl) then,
|
||||
) = __$$SubmittedImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SubmittedImplCopyWithImpl<$Res>
|
||||
extends _$LogoutFormEventCopyWithImpl<$Res, _$SubmittedImpl>
|
||||
implements _$$SubmittedImplCopyWith<$Res> {
|
||||
__$$SubmittedImplCopyWithImpl(
|
||||
_$SubmittedImpl _value,
|
||||
$Res Function(_$SubmittedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of LogoutFormEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$SubmittedImpl implements _Submitted {
|
||||
const _$SubmittedImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
class _Submitted implements LogoutFormEvent {
|
||||
const _Submitted();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Submitted);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LogoutFormEvent.submitted()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$SubmittedImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() submitted,
|
||||
}) {
|
||||
return submitted();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? submitted,
|
||||
}) {
|
||||
return submitted?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? submitted,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (submitted != null) {
|
||||
return submitted();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Submitted value) submitted,
|
||||
}) {
|
||||
return submitted(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Submitted value)? submitted,
|
||||
}) {
|
||||
return submitted?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Submitted value)? submitted,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (submitted != null) {
|
||||
return submitted(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Submitted implements LogoutFormEvent {
|
||||
const factory _Submitted() = _$SubmittedImpl;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LogoutFormState {
|
||||
Option<Either<AuthFailure, Unit>> get failureOrAuthOption =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isSubmitting => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$LogoutFormStateCopyWith<LogoutFormState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
Option<Either<AuthFailure, Unit>> get failureOrAuthOption; bool get isSubmitting;
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$LogoutFormStateCopyWith<LogoutFormState> get copyWith => _$LogoutFormStateCopyWithImpl<LogoutFormState>(this as LogoutFormState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
final _this = this as LogoutFormState;
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is LogoutFormState&&(identical(other.failureOrAuthOption, _this.failureOrAuthOption) || other.failureOrAuthOption == _this.failureOrAuthOption)&&(identical(other.isSubmitting, _this.isSubmitting) || other.isSubmitting == _this.isSubmitting));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final _this = this as LogoutFormState;
|
||||
return Object.hash(runtimeType,_this.failureOrAuthOption,_this.isSubmitting);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final _this = this as LogoutFormState;
|
||||
return 'LogoutFormState(failureOrAuthOption: ${_this.failureOrAuthOption}, isSubmitting: ${_this.isSubmitting})';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LogoutFormStateCopyWith<$Res> {
|
||||
factory $LogoutFormStateCopyWith(
|
||||
LogoutFormState value,
|
||||
$Res Function(LogoutFormState) then,
|
||||
) = _$LogoutFormStateCopyWithImpl<$Res, LogoutFormState>;
|
||||
@useResult
|
||||
$Res call({
|
||||
Option<Either<AuthFailure, Unit>> failureOrAuthOption,
|
||||
bool isSubmitting,
|
||||
});
|
||||
}
|
||||
abstract mixin class $LogoutFormStateCopyWith<$Res> {
|
||||
factory $LogoutFormStateCopyWith(LogoutFormState value, $Res Function(LogoutFormState) _then) = _$LogoutFormStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
Option<Either<AuthFailure, Unit>> failureOrAuthOption, bool isSubmitting
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$LogoutFormStateCopyWithImpl<$Res, $Val extends LogoutFormState>
|
||||
class _$LogoutFormStateCopyWithImpl<$Res>
|
||||
implements $LogoutFormStateCopyWith<$Res> {
|
||||
_$LogoutFormStateCopyWithImpl(this._value, this._then);
|
||||
_$LogoutFormStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
final LogoutFormState _self;
|
||||
final $Res Function(LogoutFormState) _then;
|
||||
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? failureOrAuthOption = null, Object? isSubmitting = null}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
failureOrAuthOption: null == failureOrAuthOption
|
||||
? _value.failureOrAuthOption
|
||||
: failureOrAuthOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<Either<AuthFailure, Unit>>,
|
||||
isSubmitting: null == isSubmitting
|
||||
? _value.isSubmitting
|
||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? failureOrAuthOption = null,Object? isSubmitting = null,}) {
|
||||
return _then(LogoutFormState(
|
||||
failureOrAuthOption: null == failureOrAuthOption ? _self.failureOrAuthOption : failureOrAuthOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<Either<AuthFailure, Unit>>,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LogoutFormStateImplCopyWith<$Res>
|
||||
implements $LogoutFormStateCopyWith<$Res> {
|
||||
factory _$$LogoutFormStateImplCopyWith(
|
||||
_$LogoutFormStateImpl value,
|
||||
$Res Function(_$LogoutFormStateImpl) then,
|
||||
) = __$$LogoutFormStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
Option<Either<AuthFailure, Unit>> failureOrAuthOption,
|
||||
bool isSubmitting,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LogoutFormStateImplCopyWithImpl<$Res>
|
||||
extends _$LogoutFormStateCopyWithImpl<$Res, _$LogoutFormStateImpl>
|
||||
implements _$$LogoutFormStateImplCopyWith<$Res> {
|
||||
__$$LogoutFormStateImplCopyWithImpl(
|
||||
_$LogoutFormStateImpl _value,
|
||||
$Res Function(_$LogoutFormStateImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? failureOrAuthOption = null, Object? isSubmitting = null}) {
|
||||
return _then(
|
||||
_$LogoutFormStateImpl(
|
||||
failureOrAuthOption: null == failureOrAuthOption
|
||||
? _value.failureOrAuthOption
|
||||
: failureOrAuthOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<Either<AuthFailure, Unit>>,
|
||||
isSubmitting: null == isSubmitting
|
||||
? _value.isSubmitting
|
||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
),
|
||||
);
|
||||
}
|
||||
/// Adds pattern-matching-related methods to [LogoutFormState].
|
||||
extension LogoutFormStatePatterns on LogoutFormState {
|
||||
/// 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 extends Object?>(TResult Function( _LogoutFormState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LogoutFormState() 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 extends Object?>(TResult Function( _LogoutFormState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LogoutFormState():
|
||||
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 extends Object?>(TResult? Function( _LogoutFormState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LogoutFormState() 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 extends Object?>(TResult Function( Option<Either<AuthFailure, Unit>> failureOrAuthOption, bool isSubmitting)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LogoutFormState() when $default != null:
|
||||
return $default(_that.failureOrAuthOption,_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 extends Object?>(TResult Function( Option<Either<AuthFailure, Unit>> failureOrAuthOption, bool isSubmitting) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LogoutFormState():
|
||||
return $default(_that.failureOrAuthOption,_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 extends Object?>(TResult? Function( Option<Either<AuthFailure, Unit>> failureOrAuthOption, bool isSubmitting)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LogoutFormState() when $default != null:
|
||||
return $default(_that.failureOrAuthOption,_that.isSubmitting);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$LogoutFormStateImpl implements _LogoutFormState {
|
||||
const _$LogoutFormStateImpl({
|
||||
required this.failureOrAuthOption,
|
||||
this.isSubmitting = false,
|
||||
});
|
||||
|
||||
@override
|
||||
final Option<Either<AuthFailure, Unit>> failureOrAuthOption;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isSubmitting;
|
||||
class _LogoutFormState implements LogoutFormState {
|
||||
const _LogoutFormState({required this.failureOrAuthOption, this.isSubmitting = false});
|
||||
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@override final Option<Either<AuthFailure, Unit>> failureOrAuthOption;
|
||||
@override@JsonKey() final bool isSubmitting;
|
||||
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$LogoutFormStateCopyWith<_LogoutFormState> get copyWith => __$LogoutFormStateCopyWithImpl<_LogoutFormState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LogoutFormState&&(identical(other.failureOrAuthOption, failureOrAuthOption) || other.failureOrAuthOption == failureOrAuthOption)&&(identical(other.isSubmitting, isSubmitting) || other.isSubmitting == isSubmitting));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(runtimeType,failureOrAuthOption,isSubmitting);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LogoutFormState(failureOrAuthOption: $failureOrAuthOption, isSubmitting: $isSubmitting)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LogoutFormStateImpl &&
|
||||
(identical(other.failureOrAuthOption, failureOrAuthOption) ||
|
||||
other.failureOrAuthOption == failureOrAuthOption) &&
|
||||
(identical(other.isSubmitting, isSubmitting) ||
|
||||
other.isSubmitting == isSubmitting));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, failureOrAuthOption, isSubmitting);
|
||||
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LogoutFormStateImplCopyWith<_$LogoutFormStateImpl> get copyWith =>
|
||||
__$$LogoutFormStateImplCopyWithImpl<_$LogoutFormStateImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class _LogoutFormState implements LogoutFormState {
|
||||
const factory _LogoutFormState({
|
||||
required final Option<Either<AuthFailure, Unit>> failureOrAuthOption,
|
||||
final bool isSubmitting,
|
||||
}) = _$LogoutFormStateImpl;
|
||||
|
||||
@override
|
||||
Option<Either<AuthFailure, Unit>> get failureOrAuthOption;
|
||||
@override
|
||||
bool get isSubmitting;
|
||||
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$LogoutFormStateImplCopyWith<_$LogoutFormStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$LogoutFormStateCopyWith<$Res> implements $LogoutFormStateCopyWith<$Res> {
|
||||
factory _$LogoutFormStateCopyWith(_LogoutFormState value, $Res Function(_LogoutFormState) _then) = __$LogoutFormStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
Option<Either<AuthFailure, Unit>> failureOrAuthOption, bool isSubmitting
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$LogoutFormStateCopyWithImpl<$Res>
|
||||
implements _$LogoutFormStateCopyWith<$Res> {
|
||||
__$LogoutFormStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _LogoutFormState _self;
|
||||
final $Res Function(_LogoutFormState) _then;
|
||||
|
||||
/// Create a copy of LogoutFormState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? failureOrAuthOption = null,Object? isSubmitting = null,}) {
|
||||
return _then(_LogoutFormState(
|
||||
failureOrAuthOption: null == failureOrAuthOption ? _self.failureOrAuthOption : failureOrAuthOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<Either<AuthFailure, Unit>>,isSubmitting: null == isSubmitting ? _self.isSubmitting : isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
part of 'logout_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class LogoutFormEvent with _$LogoutFormEvent {
|
||||
abstract class LogoutFormEvent with _$LogoutFormEvent {
|
||||
const factory LogoutFormEvent.submitted() = _Submitted;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'logout_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class LogoutFormState with _$LogoutFormState {
|
||||
abstract class LogoutFormState with _$LogoutFormState {
|
||||
const factory LogoutFormState({
|
||||
required Option<Either<AuthFailure, Unit>> failureOrAuthOption,
|
||||
@Default(false) bool isSubmitting,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'register_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class RegisterFormEvent with _$RegisterFormEvent {
|
||||
abstract class RegisterFormEvent with _$RegisterFormEvent {
|
||||
const factory RegisterFormEvent.phoneNumberChanged(String phoneNumber) =
|
||||
_PhoneNumberChanged;
|
||||
const factory RegisterFormEvent.nameChanged(String name) = _NameChanged;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'register_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class RegisterFormState with _$RegisterFormState {
|
||||
abstract class RegisterFormState with _$RegisterFormState {
|
||||
const factory RegisterFormState({
|
||||
required String phoneNumber,
|
||||
required String name,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'resend_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class ResendFormEvent with _$ResendFormEvent {
|
||||
abstract class ResendFormEvent with _$ResendFormEvent {
|
||||
const factory ResendFormEvent.phoneNumberChanged(String phoneNumber) =
|
||||
_PhoneNumberChanged;
|
||||
const factory ResendFormEvent.purposeChanged(String purpose) =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'resend_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class ResendFormState with _$ResendFormState {
|
||||
abstract class ResendFormState with _$ResendFormState {
|
||||
const factory ResendFormState({
|
||||
required String phoneNumber,
|
||||
required String purpose,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'set_password_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class SetPasswordFormEvent with _$SetPasswordFormEvent {
|
||||
abstract class SetPasswordFormEvent with _$SetPasswordFormEvent {
|
||||
const factory SetPasswordFormEvent.registrationTokenChanged(
|
||||
String registrationToken,
|
||||
) = _RegistrationTokenChanged;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'set_password_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class SetPasswordFormState with _$SetPasswordFormState {
|
||||
abstract class SetPasswordFormState with _$SetPasswordFormState {
|
||||
const factory SetPasswordFormState({
|
||||
required String registrationToken,
|
||||
required String password,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'verify_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class VerifyFormEvent with _$VerifyFormEvent {
|
||||
abstract class VerifyFormEvent with _$VerifyFormEvent {
|
||||
const factory VerifyFormEvent.registrationTokenChanged(
|
||||
String registrationToken,
|
||||
) = _RegistrationTokenChanged;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'verify_form_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class VerifyFormState with _$VerifyFormState {
|
||||
abstract class VerifyFormState with _$VerifyFormState {
|
||||
const factory VerifyFormState({
|
||||
required String registrationToken,
|
||||
required String otpCode,
|
||||
|
||||
@@ -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/customer/customer.dart';
|
||||
|
||||
part 'customer_point_loader_event.dart';
|
||||
part 'customer_point_loader_state.dart';
|
||||
part 'customer_point_loader_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class CustomerPointLoaderBloc
|
||||
extends Bloc<CustomerPointLoaderEvent, CustomerPointLoaderState> {
|
||||
final ICustomerRepository _repository;
|
||||
CustomerPointLoaderBloc(this._repository)
|
||||
: super(CustomerPointLoaderState.initial()) {
|
||||
on<CustomerPointLoaderEvent>(_onCustomerPointLoaderEvent);
|
||||
}
|
||||
|
||||
Future<void> _onCustomerPointLoaderEvent(
|
||||
CustomerPointLoaderEvent event,
|
||||
Emitter<CustomerPointLoaderState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
fetched: (e) async {
|
||||
emit(
|
||||
state.copyWith(isFetching: true, failureOptionCustomerPoint: none()),
|
||||
);
|
||||
|
||||
final result = await _repository.getPoints();
|
||||
|
||||
var data = result.fold(
|
||||
(f) => state.copyWith(failureOptionCustomerPoint: optionOf(f)),
|
||||
(customerPoint) => state.copyWith(customerPoint: customerPoint),
|
||||
);
|
||||
|
||||
emit(data.copyWith(isFetching: false));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// 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 'customer_point_loader_bloc.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
/// @nodoc
|
||||
mixin _$CustomerPointLoaderEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $CustomerPointLoaderEventCopyWith<$Res> {
|
||||
factory $CustomerPointLoaderEventCopyWith(
|
||||
CustomerPointLoaderEvent value,
|
||||
$Res Function(CustomerPointLoaderEvent) then,
|
||||
) = _$CustomerPointLoaderEventCopyWithImpl<$Res, CustomerPointLoaderEvent>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$CustomerPointLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends CustomerPointLoaderEvent
|
||||
>
|
||||
implements $CustomerPointLoaderEventCopyWith<$Res> {
|
||||
_$CustomerPointLoaderEventCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of CustomerPointLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedImplCopyWith<$Res> {
|
||||
factory _$$FetchedImplCopyWith(
|
||||
_$FetchedImpl value,
|
||||
$Res Function(_$FetchedImpl) then,
|
||||
) = __$$FetchedImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FetchedImplCopyWithImpl<$Res>
|
||||
extends _$CustomerPointLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
|
||||
implements _$$FetchedImplCopyWith<$Res> {
|
||||
__$$FetchedImplCopyWithImpl(
|
||||
_$FetchedImpl _value,
|
||||
$Res Function(_$FetchedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of CustomerPointLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FetchedImpl implements _Fetched {
|
||||
const _$FetchedImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CustomerPointLoaderEvent.fetched()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$FetchedImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({required TResult Function() fetched}) {
|
||||
return fetched();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({TResult? Function()? fetched}) {
|
||||
return fetched?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return fetched(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return fetched?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Fetched implements CustomerPointLoaderEvent {
|
||||
const factory _Fetched() = _$FetchedImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$CustomerPointLoaderState {
|
||||
CustomerPoint get customerPoint => throw _privateConstructorUsedError;
|
||||
Option<CustomerFailure> get failureOptionCustomerPoint =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of CustomerPointLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$CustomerPointLoaderStateCopyWith<CustomerPointLoaderState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $CustomerPointLoaderStateCopyWith<$Res> {
|
||||
factory $CustomerPointLoaderStateCopyWith(
|
||||
CustomerPointLoaderState value,
|
||||
$Res Function(CustomerPointLoaderState) then,
|
||||
) = _$CustomerPointLoaderStateCopyWithImpl<$Res, CustomerPointLoaderState>;
|
||||
@useResult
|
||||
$Res call({
|
||||
CustomerPoint customerPoint,
|
||||
Option<CustomerFailure> failureOptionCustomerPoint,
|
||||
bool isFetching,
|
||||
});
|
||||
|
||||
$CustomerPointCopyWith<$Res> get customerPoint;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$CustomerPointLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends CustomerPointLoaderState
|
||||
>
|
||||
implements $CustomerPointLoaderStateCopyWith<$Res> {
|
||||
_$CustomerPointLoaderStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of CustomerPointLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? customerPoint = null,
|
||||
Object? failureOptionCustomerPoint = null,
|
||||
Object? isFetching = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
customerPoint: null == customerPoint
|
||||
? _value.customerPoint
|
||||
: customerPoint // ignore: cast_nullable_to_non_nullable
|
||||
as CustomerPoint,
|
||||
failureOptionCustomerPoint: null == failureOptionCustomerPoint
|
||||
? _value.failureOptionCustomerPoint
|
||||
: failureOptionCustomerPoint // ignore: cast_nullable_to_non_nullable
|
||||
as Option<CustomerFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of CustomerPointLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$CustomerPointCopyWith<$Res> get customerPoint {
|
||||
return $CustomerPointCopyWith<$Res>(_value.customerPoint, (value) {
|
||||
return _then(_value.copyWith(customerPoint: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$CustomerPointLoaderStateImplCopyWith<$Res>
|
||||
implements $CustomerPointLoaderStateCopyWith<$Res> {
|
||||
factory _$$CustomerPointLoaderStateImplCopyWith(
|
||||
_$CustomerPointLoaderStateImpl value,
|
||||
$Res Function(_$CustomerPointLoaderStateImpl) then,
|
||||
) = __$$CustomerPointLoaderStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
CustomerPoint customerPoint,
|
||||
Option<CustomerFailure> failureOptionCustomerPoint,
|
||||
bool isFetching,
|
||||
});
|
||||
|
||||
@override
|
||||
$CustomerPointCopyWith<$Res> get customerPoint;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$CustomerPointLoaderStateImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$CustomerPointLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
_$CustomerPointLoaderStateImpl
|
||||
>
|
||||
implements _$$CustomerPointLoaderStateImplCopyWith<$Res> {
|
||||
__$$CustomerPointLoaderStateImplCopyWithImpl(
|
||||
_$CustomerPointLoaderStateImpl _value,
|
||||
$Res Function(_$CustomerPointLoaderStateImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of CustomerPointLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? customerPoint = null,
|
||||
Object? failureOptionCustomerPoint = null,
|
||||
Object? isFetching = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$CustomerPointLoaderStateImpl(
|
||||
customerPoint: null == customerPoint
|
||||
? _value.customerPoint
|
||||
: customerPoint // ignore: cast_nullable_to_non_nullable
|
||||
as CustomerPoint,
|
||||
failureOptionCustomerPoint: null == failureOptionCustomerPoint
|
||||
? _value.failureOptionCustomerPoint
|
||||
: failureOptionCustomerPoint // ignore: cast_nullable_to_non_nullable
|
||||
as Option<CustomerFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$CustomerPointLoaderStateImpl implements _CustomerPointLoaderState {
|
||||
const _$CustomerPointLoaderStateImpl({
|
||||
required this.customerPoint,
|
||||
required this.failureOptionCustomerPoint,
|
||||
this.isFetching = false,
|
||||
});
|
||||
|
||||
@override
|
||||
final CustomerPoint customerPoint;
|
||||
@override
|
||||
final Option<CustomerFailure> failureOptionCustomerPoint;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CustomerPointLoaderState(customerPoint: $customerPoint, failureOptionCustomerPoint: $failureOptionCustomerPoint, isFetching: $isFetching)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$CustomerPointLoaderStateImpl &&
|
||||
(identical(other.customerPoint, customerPoint) ||
|
||||
other.customerPoint == customerPoint) &&
|
||||
(identical(
|
||||
other.failureOptionCustomerPoint,
|
||||
failureOptionCustomerPoint,
|
||||
) ||
|
||||
other.failureOptionCustomerPoint ==
|
||||
failureOptionCustomerPoint) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
customerPoint,
|
||||
failureOptionCustomerPoint,
|
||||
isFetching,
|
||||
);
|
||||
|
||||
/// Create a copy of CustomerPointLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$CustomerPointLoaderStateImplCopyWith<_$CustomerPointLoaderStateImpl>
|
||||
get copyWith =>
|
||||
__$$CustomerPointLoaderStateImplCopyWithImpl<
|
||||
_$CustomerPointLoaderStateImpl
|
||||
>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _CustomerPointLoaderState implements CustomerPointLoaderState {
|
||||
const factory _CustomerPointLoaderState({
|
||||
required final CustomerPoint customerPoint,
|
||||
required final Option<CustomerFailure> failureOptionCustomerPoint,
|
||||
final bool isFetching,
|
||||
}) = _$CustomerPointLoaderStateImpl;
|
||||
|
||||
@override
|
||||
CustomerPoint get customerPoint;
|
||||
@override
|
||||
Option<CustomerFailure> get failureOptionCustomerPoint;
|
||||
@override
|
||||
bool get isFetching;
|
||||
|
||||
/// Create a copy of CustomerPointLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$CustomerPointLoaderStateImplCopyWith<_$CustomerPointLoaderStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
part of 'customer_point_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CustomerPointLoaderEvent with _$CustomerPointLoaderEvent {
|
||||
const factory CustomerPointLoaderEvent.fetched() = _Fetched;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
part of 'customer_point_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CustomerPointLoaderState with _$CustomerPointLoaderState {
|
||||
const factory CustomerPointLoaderState({
|
||||
required CustomerPoint customerPoint,
|
||||
required Option<CustomerFailure> failureOptionCustomerPoint,
|
||||
@Default(false) bool isFetching,
|
||||
}) = _CustomerPointLoaderState;
|
||||
|
||||
factory CustomerPointLoaderState.initial() => CustomerPointLoaderState(
|
||||
customerPoint: CustomerPoint.empty(),
|
||||
failureOptionCustomerPoint: none(),
|
||||
);
|
||||
}
|
||||
@@ -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<FerrisWheelLoaderEvent, FerrisWheelLoaderState> {
|
||||
final IGameRepository _repository;
|
||||
FerrisWheelLoaderBloc(this._repository)
|
||||
: super(FerrisWheelLoaderState.initial()) {
|
||||
on<FerrisWheelLoaderEvent>(_onFerrisWheelLoaderEvent);
|
||||
}
|
||||
|
||||
Future<void> _onFerrisWheelLoaderEvent(
|
||||
FerrisWheelLoaderEvent event,
|
||||
Emitter<FerrisWheelLoaderState> 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));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// 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';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
/// @nodoc
|
||||
mixin _$FerrisWheelLoaderEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $FerrisWheelLoaderEventCopyWith<$Res> {
|
||||
factory $FerrisWheelLoaderEventCopyWith(
|
||||
FerrisWheelLoaderEvent value,
|
||||
$Res Function(FerrisWheelLoaderEvent) then,
|
||||
) = _$FerrisWheelLoaderEventCopyWithImpl<$Res, FerrisWheelLoaderEvent>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$FerrisWheelLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends FerrisWheelLoaderEvent
|
||||
>
|
||||
implements $FerrisWheelLoaderEventCopyWith<$Res> {
|
||||
_$FerrisWheelLoaderEventCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of FerrisWheelLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedImplCopyWith<$Res> {
|
||||
factory _$$FetchedImplCopyWith(
|
||||
_$FetchedImpl value,
|
||||
$Res Function(_$FetchedImpl) then,
|
||||
) = __$$FetchedImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FetchedImplCopyWithImpl<$Res>
|
||||
extends _$FerrisWheelLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
|
||||
implements _$$FetchedImplCopyWith<$Res> {
|
||||
__$$FetchedImplCopyWithImpl(
|
||||
_$FetchedImpl _value,
|
||||
$Res Function(_$FetchedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of FerrisWheelLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FetchedImpl implements _Fetched {
|
||||
const _$FetchedImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FerrisWheelLoaderEvent.fetched()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$FetchedImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({required TResult Function() fetched}) {
|
||||
return fetched();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({TResult? Function()? fetched}) {
|
||||
return fetched?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return fetched(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return fetched?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Fetched implements FerrisWheelLoaderEvent {
|
||||
const factory _Fetched() = _$FetchedImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$FerrisWheelLoaderState {
|
||||
Game get ferrisWheel => throw _privateConstructorUsedError;
|
||||
Option<GameFailure> get failureOptionFerrisWheel =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of FerrisWheelLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$FerrisWheelLoaderStateCopyWith<FerrisWheelLoaderState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $FerrisWheelLoaderStateCopyWith<$Res> {
|
||||
factory $FerrisWheelLoaderStateCopyWith(
|
||||
FerrisWheelLoaderState value,
|
||||
$Res Function(FerrisWheelLoaderState) then,
|
||||
) = _$FerrisWheelLoaderStateCopyWithImpl<$Res, FerrisWheelLoaderState>;
|
||||
@useResult
|
||||
$Res call({
|
||||
Game ferrisWheel,
|
||||
Option<GameFailure> failureOptionFerrisWheel,
|
||||
bool isFetching,
|
||||
});
|
||||
|
||||
$GameCopyWith<$Res> get ferrisWheel;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$FerrisWheelLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends FerrisWheelLoaderState
|
||||
>
|
||||
implements $FerrisWheelLoaderStateCopyWith<$Res> {
|
||||
_$FerrisWheelLoaderStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of FerrisWheelLoaderState
|
||||
/// 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(
|
||||
_value.copyWith(
|
||||
ferrisWheel: null == ferrisWheel
|
||||
? _value.ferrisWheel
|
||||
: ferrisWheel // ignore: cast_nullable_to_non_nullable
|
||||
as Game,
|
||||
failureOptionFerrisWheel: null == failureOptionFerrisWheel
|
||||
? _value.failureOptionFerrisWheel
|
||||
: failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable
|
||||
as Option<GameFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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>(_value.ferrisWheel, (value) {
|
||||
return _then(_value.copyWith(ferrisWheel: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FerrisWheelLoaderStateImplCopyWith<$Res>
|
||||
implements $FerrisWheelLoaderStateCopyWith<$Res> {
|
||||
factory _$$FerrisWheelLoaderStateImplCopyWith(
|
||||
_$FerrisWheelLoaderStateImpl value,
|
||||
$Res Function(_$FerrisWheelLoaderStateImpl) then,
|
||||
) = __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
Game ferrisWheel,
|
||||
Option<GameFailure> failureOptionFerrisWheel,
|
||||
bool isFetching,
|
||||
});
|
||||
|
||||
@override
|
||||
$GameCopyWith<$Res> get ferrisWheel;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FerrisWheelLoaderStateImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$FerrisWheelLoaderStateCopyWithImpl<$Res, _$FerrisWheelLoaderStateImpl>
|
||||
implements _$$FerrisWheelLoaderStateImplCopyWith<$Res> {
|
||||
__$$FerrisWheelLoaderStateImplCopyWithImpl(
|
||||
_$FerrisWheelLoaderStateImpl _value,
|
||||
$Res Function(_$FerrisWheelLoaderStateImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of FerrisWheelLoaderState
|
||||
/// 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(
|
||||
_$FerrisWheelLoaderStateImpl(
|
||||
ferrisWheel: null == ferrisWheel
|
||||
? _value.ferrisWheel
|
||||
: ferrisWheel // ignore: cast_nullable_to_non_nullable
|
||||
as Game,
|
||||
failureOptionFerrisWheel: null == failureOptionFerrisWheel
|
||||
? _value.failureOptionFerrisWheel
|
||||
: failureOptionFerrisWheel // ignore: cast_nullable_to_non_nullable
|
||||
as Option<GameFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FerrisWheelLoaderStateImpl implements _FerrisWheelLoaderState {
|
||||
const _$FerrisWheelLoaderStateImpl({
|
||||
required this.ferrisWheel,
|
||||
required this.failureOptionFerrisWheel,
|
||||
this.isFetching = false,
|
||||
});
|
||||
|
||||
@override
|
||||
final Game ferrisWheel;
|
||||
@override
|
||||
final Option<GameFailure> failureOptionFerrisWheel;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FerrisWheelLoaderState(ferrisWheel: $ferrisWheel, failureOptionFerrisWheel: $failureOptionFerrisWheel, isFetching: $isFetching)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FerrisWheelLoaderStateImpl &&
|
||||
(identical(other.ferrisWheel, ferrisWheel) ||
|
||||
other.ferrisWheel == ferrisWheel) &&
|
||||
(identical(
|
||||
other.failureOptionFerrisWheel,
|
||||
failureOptionFerrisWheel,
|
||||
) ||
|
||||
other.failureOptionFerrisWheel == failureOptionFerrisWheel) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
ferrisWheel,
|
||||
failureOptionFerrisWheel,
|
||||
isFetching,
|
||||
);
|
||||
|
||||
/// Create a copy of FerrisWheelLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl>
|
||||
get copyWith =>
|
||||
__$$FerrisWheelLoaderStateImplCopyWithImpl<_$FerrisWheelLoaderStateImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class _FerrisWheelLoaderState implements FerrisWheelLoaderState {
|
||||
const factory _FerrisWheelLoaderState({
|
||||
required final Game ferrisWheel,
|
||||
required final Option<GameFailure> failureOptionFerrisWheel,
|
||||
final bool isFetching,
|
||||
}) = _$FerrisWheelLoaderStateImpl;
|
||||
|
||||
@override
|
||||
Game get ferrisWheel;
|
||||
@override
|
||||
Option<GameFailure> get failureOptionFerrisWheel;
|
||||
@override
|
||||
bool get isFetching;
|
||||
|
||||
/// Create a copy of FerrisWheelLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$FerrisWheelLoaderStateImplCopyWith<_$FerrisWheelLoaderStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
part of 'ferris_wheel_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class FerrisWheelLoaderEvent with _$FerrisWheelLoaderEvent {
|
||||
const factory FerrisWheelLoaderEvent.fetched() = _Fetched;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
part of 'ferris_wheel_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class FerrisWheelLoaderState with _$FerrisWheelLoaderState {
|
||||
const factory FerrisWheelLoaderState({
|
||||
required Game ferrisWheel,
|
||||
required Option<GameFailure> failureOptionFerrisWheel,
|
||||
@Default(false) bool isFetching,
|
||||
}) = _FerrisWheelLoaderState;
|
||||
|
||||
factory FerrisWheelLoaderState.initial() => FerrisWheelLoaderState(
|
||||
ferrisWheel: Game.empty(),
|
||||
failureOptionFerrisWheel: none(),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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 'order_detail_loader_event.dart';
|
||||
part 'order_detail_loader_state.dart';
|
||||
part 'order_detail_loader_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class OrderDetailLoaderBloc
|
||||
extends Bloc<OrderDetailLoaderEvent, OrderDetailLoaderState> {
|
||||
final IOrderRepository _repository;
|
||||
|
||||
OrderDetailLoaderBloc(this._repository)
|
||||
: super(OrderDetailLoaderState.initial()) {
|
||||
on<OrderDetailLoaderEvent>(_onOrderDetailLoaderEvent);
|
||||
}
|
||||
|
||||
Future<void> _onOrderDetailLoaderEvent(
|
||||
OrderDetailLoaderEvent event,
|
||||
Emitter<OrderDetailLoaderState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
fetched: (e) async {
|
||||
emit(state.copyWith(isFetching: true, failureOption: none()));
|
||||
|
||||
final result = await _repository.getOrderDetail(e.orderId);
|
||||
|
||||
emit(
|
||||
result
|
||||
.fold(
|
||||
(f) => state.copyWith(failureOption: optionOf(f)),
|
||||
(detail) => state.copyWith(detailOption: some(detail)),
|
||||
)
|
||||
.copyWith(isFetching: false),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
// 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 'order_detail_loader_bloc.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$OrderDetailLoaderEvent {
|
||||
|
||||
String get orderId;
|
||||
/// Create a copy of OrderDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$OrderDetailLoaderEventCopyWith<OrderDetailLoaderEvent> get copyWith => _$OrderDetailLoaderEventCopyWithImpl<OrderDetailLoaderEvent>(this as OrderDetailLoaderEvent, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
final _this = this as OrderDetailLoaderEvent;
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is OrderDetailLoaderEvent&&(identical(other.orderId, _this.orderId) || other.orderId == _this.orderId));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final _this = this as OrderDetailLoaderEvent;
|
||||
return Object.hash(runtimeType,_this.orderId);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final _this = this as OrderDetailLoaderEvent;
|
||||
return 'OrderDetailLoaderEvent(orderId: ${_this.orderId})';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $OrderDetailLoaderEventCopyWith<$Res> {
|
||||
factory $OrderDetailLoaderEventCopyWith(OrderDetailLoaderEvent value, $Res Function(OrderDetailLoaderEvent) _then) = _$OrderDetailLoaderEventCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String orderId
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$OrderDetailLoaderEventCopyWithImpl<$Res>
|
||||
implements $OrderDetailLoaderEventCopyWith<$Res> {
|
||||
_$OrderDetailLoaderEventCopyWithImpl(this._self, this._then);
|
||||
|
||||
final OrderDetailLoaderEvent _self;
|
||||
final $Res Function(OrderDetailLoaderEvent) _then;
|
||||
|
||||
/// Create a copy of OrderDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? orderId = null,}) {
|
||||
return _then(OrderDetailLoaderEvent.fetched(
|
||||
null == orderId ? _self.orderId : orderId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [OrderDetailLoaderEvent].
|
||||
extension OrderDetailLoaderEventPatterns on OrderDetailLoaderEvent {
|
||||
/// 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 extends Object?>({TResult Function( _Fetched value)? fetched,required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Fetched() when fetched != null:
|
||||
return fetched(_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 extends Object?>({required TResult Function( _Fetched value) fetched,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Fetched():
|
||||
return fetched(_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 extends Object?>({TResult? Function( _Fetched value)? fetched,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Fetched() when fetched != null:
|
||||
return fetched(_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 extends Object?>({TResult Function( String orderId)? fetched,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Fetched() when fetched != null:
|
||||
return fetched(_that.orderId);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 extends Object?>({required TResult Function( String orderId) fetched,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Fetched():
|
||||
return fetched(_that.orderId);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 extends Object?>({TResult? Function( String orderId)? fetched,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Fetched() when fetched != null:
|
||||
return fetched(_that.orderId);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _Fetched implements OrderDetailLoaderEvent {
|
||||
const _Fetched(this.orderId);
|
||||
|
||||
|
||||
@override final String orderId;
|
||||
|
||||
/// Create a copy of OrderDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$FetchedCopyWith<_Fetched> get copyWith => __$FetchedCopyWithImpl<_Fetched>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Fetched&&(identical(other.orderId, orderId) || other.orderId == orderId));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(runtimeType,orderId);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'OrderDetailLoaderEvent.fetched(orderId: $orderId)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$FetchedCopyWith<$Res> implements $OrderDetailLoaderEventCopyWith<$Res> {
|
||||
factory _$FetchedCopyWith(_Fetched value, $Res Function(_Fetched) _then) = __$FetchedCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String orderId
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$FetchedCopyWithImpl<$Res>
|
||||
implements _$FetchedCopyWith<$Res> {
|
||||
__$FetchedCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _Fetched _self;
|
||||
final $Res Function(_Fetched) _then;
|
||||
|
||||
/// Create a copy of OrderDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? orderId = null,}) {
|
||||
return _then(_Fetched(
|
||||
null == orderId ? _self.orderId : orderId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$OrderDetailLoaderState {
|
||||
|
||||
Option<CustomerOrderDetail> get detailOption; Option<WalletFailure> get failureOption; bool get isFetching;
|
||||
/// Create a copy of OrderDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$OrderDetailLoaderStateCopyWith<OrderDetailLoaderState> get copyWith => _$OrderDetailLoaderStateCopyWithImpl<OrderDetailLoaderState>(this as OrderDetailLoaderState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
final _this = this as OrderDetailLoaderState;
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is OrderDetailLoaderState&&(identical(other.detailOption, _this.detailOption) || other.detailOption == _this.detailOption)&&(identical(other.failureOption, _this.failureOption) || other.failureOption == _this.failureOption)&&(identical(other.isFetching, _this.isFetching) || other.isFetching == _this.isFetching));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final _this = this as OrderDetailLoaderState;
|
||||
return Object.hash(runtimeType,_this.detailOption,_this.failureOption,_this.isFetching);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final _this = this as OrderDetailLoaderState;
|
||||
return 'OrderDetailLoaderState(detailOption: ${_this.detailOption}, failureOption: ${_this.failureOption}, isFetching: ${_this.isFetching})';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $OrderDetailLoaderStateCopyWith<$Res> {
|
||||
factory $OrderDetailLoaderStateCopyWith(OrderDetailLoaderState value, $Res Function(OrderDetailLoaderState) _then) = _$OrderDetailLoaderStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
Option<CustomerOrderDetail> detailOption, Option<WalletFailure> failureOption, bool isFetching
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$OrderDetailLoaderStateCopyWithImpl<$Res>
|
||||
implements $OrderDetailLoaderStateCopyWith<$Res> {
|
||||
_$OrderDetailLoaderStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final OrderDetailLoaderState _self;
|
||||
final $Res Function(OrderDetailLoaderState) _then;
|
||||
|
||||
/// Create a copy of OrderDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? detailOption = null,Object? failureOption = null,Object? isFetching = null,}) {
|
||||
return _then(OrderDetailLoaderState(
|
||||
detailOption: null == detailOption ? _self.detailOption : detailOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<CustomerOrderDetail>,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<WalletFailure>,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [OrderDetailLoaderState].
|
||||
extension OrderDetailLoaderStatePatterns on OrderDetailLoaderState {
|
||||
/// 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 extends Object?>(TResult Function( _OrderDetailLoaderState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _OrderDetailLoaderState() 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 extends Object?>(TResult Function( _OrderDetailLoaderState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _OrderDetailLoaderState():
|
||||
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 extends Object?>(TResult? Function( _OrderDetailLoaderState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _OrderDetailLoaderState() 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 extends Object?>(TResult Function( Option<CustomerOrderDetail> detailOption, Option<WalletFailure> failureOption, bool isFetching)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _OrderDetailLoaderState() when $default != null:
|
||||
return $default(_that.detailOption,_that.failureOption,_that.isFetching);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 extends Object?>(TResult Function( Option<CustomerOrderDetail> detailOption, Option<WalletFailure> failureOption, bool isFetching) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _OrderDetailLoaderState():
|
||||
return $default(_that.detailOption,_that.failureOption,_that.isFetching);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 extends Object?>(TResult? Function( Option<CustomerOrderDetail> detailOption, Option<WalletFailure> failureOption, bool isFetching)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _OrderDetailLoaderState() when $default != null:
|
||||
return $default(_that.detailOption,_that.failureOption,_that.isFetching);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _OrderDetailLoaderState implements OrderDetailLoaderState {
|
||||
const _OrderDetailLoaderState({required this.detailOption, required this.failureOption, this.isFetching = false});
|
||||
|
||||
|
||||
@override final Option<CustomerOrderDetail> detailOption;
|
||||
@override final Option<WalletFailure> failureOption;
|
||||
@override@JsonKey() final bool isFetching;
|
||||
|
||||
/// Create a copy of OrderDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$OrderDetailLoaderStateCopyWith<_OrderDetailLoaderState> get copyWith => __$OrderDetailLoaderStateCopyWithImpl<_OrderDetailLoaderState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _OrderDetailLoaderState&&(identical(other.detailOption, detailOption) || other.detailOption == detailOption)&&(identical(other.failureOption, failureOption) || other.failureOption == failureOption)&&(identical(other.isFetching, isFetching) || other.isFetching == isFetching));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(runtimeType,detailOption,failureOption,isFetching);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'OrderDetailLoaderState(detailOption: $detailOption, failureOption: $failureOption, isFetching: $isFetching)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$OrderDetailLoaderStateCopyWith<$Res> implements $OrderDetailLoaderStateCopyWith<$Res> {
|
||||
factory _$OrderDetailLoaderStateCopyWith(_OrderDetailLoaderState value, $Res Function(_OrderDetailLoaderState) _then) = __$OrderDetailLoaderStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
Option<CustomerOrderDetail> detailOption, Option<WalletFailure> failureOption, bool isFetching
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$OrderDetailLoaderStateCopyWithImpl<$Res>
|
||||
implements _$OrderDetailLoaderStateCopyWith<$Res> {
|
||||
__$OrderDetailLoaderStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _OrderDetailLoaderState _self;
|
||||
final $Res Function(_OrderDetailLoaderState) _then;
|
||||
|
||||
/// Create a copy of OrderDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? detailOption = null,Object? failureOption = null,Object? isFetching = null,}) {
|
||||
return _then(_OrderDetailLoaderState(
|
||||
detailOption: null == detailOption ? _self.detailOption : detailOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<CustomerOrderDetail>,failureOption: null == failureOption ? _self.failureOption : failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<WalletFailure>,isFetching: null == isFetching ? _self.isFetching : isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -0,0 +1,6 @@
|
||||
part of 'order_detail_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
abstract class OrderDetailLoaderEvent with _$OrderDetailLoaderEvent {
|
||||
const factory OrderDetailLoaderEvent.fetched(String orderId) = _Fetched;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
part of 'order_detail_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
abstract class OrderDetailLoaderState with _$OrderDetailLoaderState {
|
||||
const factory OrderDetailLoaderState({
|
||||
required Option<CustomerOrderDetail> detailOption,
|
||||
required Option<WalletFailure> failureOption,
|
||||
@Default(false) bool isFetching,
|
||||
}) = _OrderDetailLoaderState;
|
||||
|
||||
factory OrderDetailLoaderState.initial() =>
|
||||
OrderDetailLoaderState(detailOption: none(), failureOption: none());
|
||||
}
|
||||