Files
orion/clients/terminal-android/app/build.gradle.kts
Samir Boulahtit ac5f46cff3
Some checks failed
CI / ruff (push) Successful in 16s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
feat(android-terminal): Phase E — offline queue + sync
Stamp / earn / enroll actions performed without connectivity now persist
in the existing pending_transactions Room table and drain via a
SyncWorker as soon as the network constraint is satisfied. Redemption
stays online-only (server needs the authoritative balance — queueing
would let cashiers redeem rewards customers have already spent).

Pieces:

- Hilt-Work plumbing: androidx.hilt:hilt-compiler KSP processor wired,
  RewardFlowApp now implements Configuration.Provider with the injected
  HiltWorkerFactory, AndroidManifest disables the WorkManager auto-init
  via the AndroidX startup tools:node="remove" pattern. Bumped Dagger
  to 2.55 so its compiler can read Kotlin 2.1 metadata — 2.51 crashed
  with "Unable to read Kotlin metadata due to unsupported metadata
  version" once we added @Inject lateinit var on the Application class.

- data/sync/SyncWorker.kt — @HiltWorker CoroutineWorker that drains the
  queue FIFO. Per row: HTTP error → permanent markFailed; IOException
  → Result.retry(); success → markSynced + sweep at end of run.

- data/repository/QueueRepository.kt — single entrypoint for "queue
  this offline action". queueStamp / queuePointsEarn / queueEnroll
  Moshi-serialize the request body and enqueueUniqueWork(KEEP) the
  worker under a NetworkType.CONNECTED constraint with 30s exponential
  backoff. scheduleSync() is idempotent.

- TerminalViewModel: runAction(block, queueOnNetworkFailure) — on
  IOException AND a queue lambda is provided, queue + emit
  ActionResult.Queued. HttpException paths still surface the server's
  message inline. Stamp / Earn / Enroll go through the queue path;
  redeem actions pass null. setStaffPinId records who initiated each
  queued row. init schedules a sync so anything left from a previous
  session drains on screen entry. pendingSyncCount is combined into
  state via Flow<Int> from the DAO; when it drops to 0 we refresh the
  recent-transactions feed so the UI shows what just synced.

- TerminalScreen: PendingSyncPill in the top bar (only visible when
  count > 0) gives the cashier feedback that a queued action is
  waiting + that the queue is draining live.

Cleartext + readable HTTP errors from yesterday remain in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 23:54:42 +02:00

113 lines
3.2 KiB
Kotlin

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}
android {
namespace = "lu.rewardflow.terminal"
compileSdk = 35
defaultConfig {
applicationId = "lu.rewardflow.terminal"
minSdk = 26 // Android 8.0 — covers 95%+ of tablets
targetSdk = 35
versionCode = 1
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
buildConfigField("String", "DEFAULT_API_URL", "\"http://10.0.2.2:8000\"")
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
buildConfigField("String", "DEFAULT_API_URL", "\"https://rewardflow.lu\"")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
buildConfig = true
}
}
dependencies {
// AndroidX Core
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
// Compose
implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)
implementation(libs.compose.ui.graphics)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.compose.material3)
implementation(libs.compose.material.icons)
implementation(libs.activity.compose)
implementation(libs.navigation.compose)
implementation(libs.lifecycle.runtime.compose)
implementation(libs.lifecycle.viewmodel.compose)
debugImplementation(libs.compose.ui.tooling)
// Networking (Retrofit + Moshi)
implementation(libs.retrofit)
implementation(libs.retrofit.moshi)
implementation(libs.okhttp)
implementation(libs.okhttp.logging)
implementation(libs.moshi)
ksp(libs.moshi.codegen)
// Room (offline database)
implementation(libs.room.runtime)
implementation(libs.room.ktx)
ksp(libs.room.compiler)
// WorkManager (background sync)
implementation(libs.work.runtime.ktx)
// Hilt (dependency injection)
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
implementation(libs.hilt.navigation.compose)
implementation(libs.hilt.work)
ksp(libs.androidx.hilt.compiler)
// CameraX + ML Kit (QR scanning)
implementation(libs.camera.core)
implementation(libs.camera.camera2)
implementation(libs.camera.lifecycle)
implementation(libs.camera.view)
implementation(libs.mlkit.barcode)
// DataStore (device config persistence)
implementation(libs.datastore.preferences)
// bcrypt (verify staff PIN hashes locally — pure Java, no native deps)
implementation(libs.bcrypt)
// Testing
testImplementation(libs.junit)
androidTestImplementation(libs.junit.android)
androidTestImplementation(libs.espresso.core)
}