# Pantree Android Android client for Pantree — the app that tells you what you can cook with what you already have. ## Architecture - **MVVM** — ViewModel + StateFlow, no LiveData - **Hilt** — dependency injection - **Room** — local cache (pantry items, recipes, shopping lists) - **Retrofit + OkHttp** — network layer with JWT interceptor - **Jetpack Compose** — declarative UI, Material 3 - **EncryptedSharedPreferences** — JWT stored at rest - **DataStore** — last-sync timestamp ## Module Structure ``` app/src/main/java/com/pantree/app/ ├── data/ │ ├── local/ # Room DB, DAOs, entities, TokenStore, SyncPreferences │ ├── model/ # API DTOs (request/response) │ ├── remote/ # ApiService (Retrofit), AuthInterceptor │ └── repository/ # AuthRepository, PantryRepository, RecipeRepository, │ # ShoppingListRepository, SyncRepository ├── di/ # Hilt AppModule ├── sync/ # SyncManager (lifecycle observer) ├── ui/ │ ├── components/ # LoadingState, ErrorState, EmptyState, OfflineBanner, PantreeTopBar │ ├── navigation/ # Screen sealed class, PantreeNavGraph │ ├── screens/ │ │ ├── auth/ # SplashScreen, SignInScreen, SignUpScreen, │ │ │ # ForgotPasswordScreen, ResetPasswordScreen + AuthViewModel │ │ ├── pantry/ # PantryScreen + PantryViewModel │ │ ├── recipe/ # RecipesScreen, RecipeDetailScreen + RecipeViewModel │ │ ├── shopping/ # ShoppingListsScreen, ShoppingListDetailScreen + ShoppingListViewModel │ │ └── settings/ # SettingsScreen + SettingsViewModel │ └── theme/ # Color, Type, Theme └── util/ # Result, safeApiCall, toUserMessage ``` ## UI States Every screen handles all four states: | State | Implementation | |-------|---------------| | **Loading** | `LoadingState` composable — spinner + contextual message | | **Error** | `ErrorState` composable — emoji + message + retry button | | **Empty** | `EmptyState` composable — emoji + title + subtitle + optional CTA | | **Success** | Full content with `LazyColumn` / detail view | Offline: cached data shown read-only, `OfflineBanner` displayed. ## Sync Strategy - `SyncManager` implements `DefaultLifecycleObserver` — triggers on `onStart` (app open + foreground) - Delta sync via `GET /sync?since=` - Server timestamp stored in DataStore, used as `since` on next sync - Room cache updated; UI observes `Flow>` — updates automatically ## Setup 1. Set `BASE_URL` in `app/build.gradle` debug/release buildConfigFields 2. Set `GOOGLE_WEB_CLIENT_ID` for Google Sign-In 3. Run backend (`npm run dev` from repo root) 4. `./gradlew assembleDebug` ## Running Tests ```bash # Unit tests ./gradlew test # Instrumented tests (requires emulator/device) ./gradlew connectedAndroidTest ```