# App Boilerplate A modular, offline-first Flutter boilerplate for apps that store, sync, and share media and metadata across centralized (Immich, Firebase) and decentralized (Nostr) systems. ## Phase 6 - User Session Management - User login, logout, and session switching - Per-user data isolation with separate storage paths - Cache clearing on logout - Integration with local storage and sync engine - Comprehensive unit tests ## Phase 5 - Relay Management UI - User interface for managing Nostr relays - View, add, remove, and monitor relay health - Manual sync trigger integration - Modular controller-based architecture - Comprehensive UI tests ## Phase 4 - Sync Engine - Coordinates data synchronization between local storage, Immich, and Nostr - Conflict resolution strategies (useLocal, useRemote, useLatest, merge) - Offline queue with automatic retry - Priority-based operation processing - Comprehensive unit and integration tests ## Phase 3 - Nostr Integration - Nostr protocol service for decentralized metadata synchronization - Keypair generation and event publishing - Multi-relay support for metadata syncing - Comprehensive unit tests ## Phase 2 - Immich Integration - Immich API service for uploading and fetching images - Automatic metadata storage in local database - Offline-first behavior with local caching - Comprehensive unit tests ## Phase 1 - Local Storage & Caching - Local storage service with SQLite database - CRUD operations for items - Image caching functionality - Comprehensive unit tests ## Quick Start ```bash # Install dependencies flutter pub get # Run tests flutter test # Run app flutter run ``` ## Local Storage & Caching Service for local storage and caching operations. Provides CRUD operations for items stored in SQLite, image caching with automatic download/storage, and cache hit/miss handling. Modular design with no UI dependencies. **Files:** - `lib/data/local/local_storage_service.dart` - Main service class - `lib/data/local/models/item.dart` - Item data model - `test/data/local/local_storage_service_test.dart` - Unit tests **Key Methods:** `initialize()`, `insertItem()`, `getItem()`, `getAllItems()`, `updateItem()`, `deleteItem()`, `getCachedImage()`, `clearImageCache()`, `close()` ## Immich Integration Service for interacting with Immich API. Uploads images, fetches asset lists, and automatically stores metadata in local database. Offline-first design allows access to cached metadata without network. **Configuration:** Edit `lib/config/config_loader.dart` to set `immichBaseUrl` and `immichApiKey` for dev/prod environments (lines 40-41 for dev, lines 47-48 for prod). **Files:** - `lib/data/immich/immich_service.dart` - Main service class - `lib/data/immich/models/immich_asset.dart` - Asset model - `lib/data/immich/models/upload_response.dart` - Upload response model - `test/data/immich/immich_service_test.dart` - Unit tests **Key Methods:** `uploadImage()`, `fetchAssets()`, `getCachedAsset()`, `getCachedAssets()` ## Nostr Integration Service for decentralized metadata synchronization using Nostr protocol. Generates keypairs, publishes events, and syncs metadata across multiple relays. Modular design allows testing without real relay connections. **Files:** - `lib/data/nostr/nostr_service.dart` - Main service class - `lib/data/nostr/models/nostr_keypair.dart` - Keypair model - `lib/data/nostr/models/nostr_event.dart` - Event model - `lib/data/nostr/models/nostr_relay.dart` - Relay model - `test/data/nostr/nostr_service_test.dart` - Unit tests **Key Methods:** `generateKeyPair()`, `addRelay()`, `connectRelay()`, `publishEvent()`, `syncMetadata()`, `dispose()` ## Sync Engine Engine for coordinating data synchronization between local storage, Immich, and Nostr. Handles conflict resolution, offline queuing, and automatic retries. Processes operations by priority with configurable conflict resolution strategies. **Files:** - `lib/data/sync/sync_engine.dart` - Main sync engine class - `lib/data/sync/models/sync_status.dart` - Status and priority enums - `lib/data/sync/models/sync_operation.dart` - Operation model - `test/data/sync/sync_engine_test.dart` - Unit and integration tests **Key Methods:** `syncToImmich()`, `syncFromImmich()`, `syncToNostr()`, `syncAll()`, `queueOperation()`, `resolveConflict()`, `getPendingOperations()` **Conflict Resolution:** `useLocal`, `useRemote`, `useLatest`, `merge` - set via `setConflictResolution()` ## Relay Management UI User interface for managing Nostr relays. View configured relays, add/remove relays, monitor connection health, and trigger manual syncs. Modular design with controller-based state management for testability. **Files:** - `lib/ui/relay_management/relay_management_screen.dart` - Main UI screen - `lib/ui/relay_management/relay_management_controller.dart` - State management controller - `test/ui/relay_management/relay_management_screen_test.dart` - UI tests - `test/ui/relay_management/relay_management_controller_test.dart` - Controller tests **Key Features:** Add/remove relays, connect/disconnect, health monitoring, manual sync trigger, error handling **Usage:** Navigate to "Manage Relays" from the main screen after initialization. ## User Session Management Service for managing user sessions, login, logout, and session isolation. Provides per-user data isolation with separate storage paths and cache directories. Clears cached data on logout and integrates with local storage and sync engine. **Files:** - `lib/data/session/session_service.dart` - Main session management service - `lib/data/session/models/user.dart` - User model - `test/data/session/session_service_test.dart` - Unit tests **Key Methods:** `login()`, `logout()`, `switchSession()`, `getCurrentUserDbPath()`, `getCurrentUserCacheDir()` **Features:** Per-user storage isolation, cache clearing on logout, session switching with data preservation, integration with local storage **Usage:** Initialize `SessionService` with `LocalStorageService` and optional `SyncEngine`. Call `login()` to start a session, `logout()` to end it, and `switchSession()` to change users. ## Configuration **Configuration uses `.env` files for sensitive values (API keys, URLs) with fallback defaults in `lib/config/config_loader.dart`.** ### Setup .env File 1. Copy `.env.example` to `.env` in the project root: ```bash cp .env.example .env ``` 2. Edit `.env` and fill in your actual values: - `IMMICH_BASE_URL` - Your Immich server URL - `IMMICH_API_KEY_DEV` - Your development Immich API key - `IMMICH_API_KEY_PROD` - Your production Immich API key - `NOSTR_RELAYS_DEV` - Comma-separated Nostr relay URLs for dev - `NOSTR_RELAYS_PROD` - Comma-separated Nostr relay URLs for prod - Other configuration values as needed **Important:** The `.env` file is in `.gitignore` and should never be committed to version control. Only commit `.env.example` as a template. ### Environment Variables The app uses: - **`.env` file** (recommended) - Loaded at runtime, falls back to defaults if not found - **`--dart-define`** - For environment selection: ```bash # Set environment at runtime flutter run --dart-define=ENV=prod ``` If `.env` file is not found or variables are missing, the app uses default values from `lib/config/config_loader.dart`. ### Available Environments - `dev` - Development (default): Logging enabled, dev API URL - `prod` - Production: Logging disabled, production API URL ## Running the App ### Android Emulator **Important:** Wait for emulator to fully boot (30-60 seconds on cold boot). 1. Start emulator from Android Studio AVD Manager 2. Wait for boot animation to complete and home screen appears 3. Run: `flutter run` ### iOS Simulator (macOS) ```bash open -a Simulator flutter run ``` ## Running Tests ```bash # Run all tests flutter test # Run with coverage flutter test --coverage ``` **Important:** Tests must be run separately - `flutter run` does not execute tests. ## Project Structure ``` lib/ ├── config/ │ ├── app_config.dart │ └── config_loader.dart ├── data/ │ ├── local/ │ │ ├── local_storage_service.dart │ │ └── models/ │ │ └── item.dart │ ├── immich/ │ │ ├── immich_service.dart │ │ └── models/ │ │ ├── immich_asset.dart │ │ └── upload_response.dart │ ├── nostr/ │ │ ├── nostr_service.dart │ │ └── models/ │ │ ├── nostr_keypair.dart │ │ ├── nostr_event.dart │ │ └── nostr_relay.dart │ ├── session/ │ │ ├── session_service.dart │ │ └── models/ │ │ └── user.dart │ └── sync/ │ ├── sync_engine.dart │ └── models/ │ ├── sync_status.dart │ └── sync_operation.dart ├── ui/ │ └── relay_management/ │ ├── relay_management_screen.dart │ └── relay_management_controller.dart └── main.dart test/ ├── config/ │ └── config_loader_test.dart └── data/ ├── local/ │ └── local_storage_service_test.dart ├── immich/ │ └── immich_service_test.dart ├── nostr/ │ └── nostr_service_test.dart ├── session/ │ └── session_service_test.dart ├── sync/ │ └── sync_engine_test.dart └── ui/ └── relay_management/ ├── relay_management_screen_test.dart └── relay_management_controller_test.dart ```