- Build config: build.gradle (app + root), settings.gradle, AndroidManifest - Application entry: PantreeApplication (Hilt), MainActivity (Compose host) - Data layer: - ApiService (Retrofit interface, all 20 endpoints) - ApiModels (all request/response DTOs) - AuthInterceptor (JWT Bearer header injection) - TokenStore (EncryptedSharedPreferences) - SyncPreferences (DataStore, last-sync timestamp) - PantreeDatabase (Room, 5 entities) - Entities: PantryItemEntity, RecipeEntity, RecipeIngredientEntity, ShoppingListEntity, ShoppingListItemEntity - DAOs: PantryDao, RecipeDao, ShoppingListDao - Repositories: AuthRepository, PantryRepository, RecipeRepository, ShoppingListRepository, SyncRepository - DI: AppModule (Hilt, provides OkHttp/Retrofit/Room/DAOs) - Util: Result<T> sealed class, safeApiCall, toUserMessage (human-readable error mapping) - Sync: SyncManager (DefaultLifecycleObserver, triggers on app open/foreground) - Theme: Color, Type, Theme (Material 3, light + dark) - Navigation: Screen sealed class, PantreeNavGraph (deep links for password reset) - Shared components: LoadingState, ErrorState, EmptyState, OfflineBanner, PantreeTopBar - Auth screens: SplashScreen, SignInScreen, SignUpScreen, ForgotPasswordScreen, ResetPasswordScreen + AuthViewModel - Pantry screen: PantryScreen (list/add/edit/delete dialogs) + PantryViewModel - Recipe screens: RecipesScreen (filter chips, search, availability badges) + RecipeDetailScreen (scale 1x/2x/3x, ingredient pantry status) + RecipeViewModel - Shopping screens: ShoppingListsScreen + ShoppingListDetailScreen (check-off, progress bar, merge feedback, add-from-recipes) + ShoppingListViewModel - Settings screen: sign out + account deletion (15-day window copy) + SettingsViewModel - Tests: ResultTest (unit), RecipeDetailScreenTest (unit), PantryScreenTest (instrumented) - README: architecture, module structure, UI states table, sync strategy, setup Every screen handles loading / error / empty / success states. Offline: cached Room data shown read-only.
77 lines
3.0 KiB
Markdown
77 lines
3.0 KiB
Markdown
# 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<T>, 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=<last_timestamp>`
|
|
- Server timestamp stored in DataStore, used as `since` on next sync
|
|
- Room cache updated; UI observes `Flow<List<Entity>>` — 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
|
|
```
|