You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

17 KiB

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 8 - Navigation & UI Scaffold

  • Complete navigation structure connecting all main modules
  • Bottom navigation bar with 5 main screens: Home, Immich, Nostr Events, Session, Settings
  • Route guards requiring authentication for protected screens
  • Placeholder screens for all modules ready for custom UI implementation
  • Modular navigation architecture with testable components
  • Comprehensive UI tests for navigation and route guards

Quick Start

# 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.

Firebase Layer

Optional Firebase integration providing cloud sync, storage, authentication, push notifications, and analytics. Fully modular - can be enabled or disabled without affecting offline-first functionality. Integrates with local storage and session management to maintain offline-first behavior.

Files:

  • lib/data/firebase/firebase_service.dart - Main Firebase service
  • lib/data/firebase/models/firebase_config.dart - Firebase configuration model
  • test/data/firebase/firebase_service_test.dart - Unit tests

Key Methods: initialize(), loginWithEmailPassword(), logout(), syncItemsToFirestore(), syncItemsFromFirestore(), uploadFile(), getFcmToken(), logEvent()

Features: Firestore cloud sync, Firebase Storage for media, Firebase Auth for authentication, Firebase Cloud Messaging for push notifications, Firebase Analytics for analytics, all optional and modular

Usage: Create FirebaseService with FirebaseConfig (disabled by default). Pass to SessionService for automatic sync on login/logout. Initialize Firebase with initialize() before use. All services gracefully handle being disabled.

Note: Firebase requires actual Firebase project setup with google-services.json (Android) and GoogleService-Info.plist (iOS) configuration files. The service handles missing configuration gracefully and maintains offline-first behavior.

Navigation & UI Scaffold

Complete navigation structure connecting all main modules with bottom navigation bar. Includes route guards for protected screens and placeholder screens ready for custom UI implementation.

Files:

  • lib/ui/navigation/main_navigation_scaffold.dart - Main navigation scaffold with bottom nav
  • lib/ui/navigation/app_router.dart - Router with route guards and route generation
  • lib/ui/home/home_screen.dart - Home screen (local storage items)
  • lib/ui/immich/immich_screen.dart - Immich media screen (placeholder)
  • lib/ui/nostr_events/nostr_events_screen.dart - Nostr events screen (placeholder)
  • lib/ui/session/session_screen.dart - Session management (login/logout)
  • lib/ui/settings/settings_screen.dart - Settings screen
  • test/ui/navigation/main_navigation_scaffold_test.dart - Navigation tests

Navigation Structure:

  • Home - Local storage and cached content (no auth required)
  • Immich - Immich media integration (requires login)
  • Nostr Events - Nostr events display (requires login)
  • Session - User login/logout (no auth required)
  • Settings - App settings and Relay Management access (no auth required)

Route Guards: Immich and Nostr Events screens require authentication. Unauthenticated users see a login prompt with option to navigate to Session screen.

Usage: The app automatically uses MainNavigationScaffold after initialization. All services are passed to the scaffold for dependency injection. Customize placeholder screens by editing the respective screen files in lib/ui/.

Running UI Tests:

flutter test test/ui/navigation/main_navigation_scaffold_test.dart

Configuration

Configuration uses .env files for sensitive values (API keys, URLs) with fallback defaults in lib/config/config_loader.dart.

Setup .env File

  1. Create .env.example file in the project root with the following template:

    # Application Configuration
    APP_NAME=app_boilerplate
    
    # Immich Configuration
    IMMICH_BASE_URL=https://photos.satoshinakamoto.win
    IMMICH_API_KEY_DEV=your-dev-api-key-here
    IMMICH_API_KEY_PROD=your-prod-api-key-here
    
    # Nostr Relays (comma-separated list)
    NOSTR_RELAYS_DEV=wss://nostrum.satoshinakamoto.win,wss://nos.lol
    NOSTR_RELAYS_PROD=wss://relay.damus.io
    
    # API Configuration
    API_BASE_URL_DEV=https://api-dev.example.com
    API_BASE_URL_PROD=https://api.example.com
    
    # Logging
    ENABLE_LOGGING_DEV=true
    ENABLE_LOGGING_PROD=false
    
    # Firebase Configuration (optional)
    FIREBASE_ENABLED=false
    FIREBASE_FIRESTORE_ENABLED=true
    FIREBASE_STORAGE_ENABLED=true
    FIREBASE_AUTH_ENABLED=true
    FIREBASE_MESSAGING_ENABLED=true
    FIREBASE_ANALYTICS_ENABLED=true
    
  2. Copy .env.example to .env and fill in your actual values:

    cp .env.example .env
    
  3. Edit .env with your actual configuration values.

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:
# 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

App Name Configuration

1. Set APP_NAME in your .env file

2. Run the script

./scripts/set_app_name.sh

To make this easier, you can use the provided script that reads APP_NAME from .env and automatically updates all platform files:

# Make sure APP_NAME is set in your .env file first
./scripts/set_app_name.sh

This script will:

  1. Read APP_NAME from your .env file
  2. Update android/app/src/main/AndroidManifest.xml
  3. Update macos/Runner/Configs/AppInfo.xcconfig

Manual Setup: If you prefer to set it manually, just update the files listed above directly.

App Icon Configuration

App icons are platform-specific and must be placed in the correct directories with the correct formats.

Android Icons

Location: android/app/src/main/res/mipmap-*/

Format: PNG files

Required Sizes:

  • mipmap-mdpi/ic_launcher.png - 48x48 px
  • mipmap-hdpi/ic_launcher.png - 72x72 px
  • mipmap-xhdpi/ic_launcher.png - 96x96 px
  • mipmap-xxhdpi/ic_launcher.png - 144x144 px
  • mipmap-xxxhdpi/ic_launcher.png - 192x192 px

Instructions:

  1. Create your app icon as a square image (recommended: 1024x1024 px source)
  2. Generate all required sizes using an icon generator tool (e.g., App Icon Generator)
  3. Replace the existing ic_launcher.png files in each mipmap-* directory
  4. The icon is referenced in AndroidManifest.xml as @mipmap/ic_launcher

Best Practices:

  • Use PNG format (no transparency for launcher icons on some Android versions)
  • Keep icon simple and recognizable at small sizes
  • Follow Material Design guidelines for Android icons
  • Ensure icon works on both light and dark backgrounds

macOS Icons

Location: macos/Runner/Assets.xcassets/AppIcon.appiconset/

Format: PNG files

Required Sizes:

  • app_icon_16.png - 16x16 px
  • app_icon_32.png - 32x32 px
  • app_icon_64.png - 64x64 px
  • app_icon_128.png - 128x128 px
  • app_icon_256.png - 256x256 px
  • app_icon_512.png - 512x512 px
  • app_icon_1024.png - 1024x1024 px

Instructions:

  1. Create your app icon as a square image (recommended: 1024x1024 px source)
  2. Generate all required sizes
  3. Replace the existing PNG files in AppIcon.appiconset/ directory
  4. The Contents.json file defines which sizes map to which files - update if needed

Best Practices:

  • Use PNG format
  • macOS icons can have transparency
  • Follow macOS Human Interface Guidelines
  • Icon should be recognizable at 16x16 size

iOS Icons (if adding iOS support)

Location: ios/Runner/Assets.xcassets/AppIcon.appiconset/

Format: PNG files (no transparency for some sizes)

Required Sizes: iOS requires many sizes. Use Xcode's App Icon set or a tool like App Icon Generator to generate all required sizes automatically.

Best Practices:

  • Use PNG format
  • Some sizes require no transparency (check iOS guidelines)
  • Follow iOS Human Interface Guidelines
  • Generate all sizes from a 1024x1024 px source

Icon Generation Tools

Recommended tools for generating all required icon sizes:

  • AppIcon.co - Online tool, supports multiple platforms
  • IconKitchen - Google's icon generator
  • MakeAppIcon - Generates all sizes from one image
  • Xcode (for macOS/iOS) - Built-in asset catalog editor

Quick Setup

  1. Prepare your icon: Create a 1024x1024 px square PNG image
  2. Generate sizes: Use one of the tools above to generate all required sizes
  3. Replace files: Copy generated icons to the appropriate directories
  4. Test: Run the app and verify icons appear correctly

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)

open -a Simulator
flutter run

Running Tests

# 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
 │    ├── firebase/
 │    │    ├── firebase_service.dart
 │    │    └── models/
 │    │         └── firebase_config.dart
 │    └── sync/
 │         ├── sync_engine.dart
 │         └── models/
 │              ├── sync_status.dart
 │              └── sync_operation.dart
 ├── ui/
 │    ├── navigation/
 │    │    ├── main_navigation_scaffold.dart
 │    │    └── app_router.dart
 │    ├── home/
 │    │    └── home_screen.dart
 │    ├── immich/
 │    │    └── immich_screen.dart
 │    ├── nostr_events/
 │    │    └── nostr_events_screen.dart
 │    ├── session/
 │    │    └── session_screen.dart
 │    ├── settings/
 │    │    └── settings_screen.dart
 │    └── 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
      ├── firebase/
      │    └── firebase_service_test.dart
      ├── sync/
      │    └── sync_engine_test.dart
      └── ui/
           ├── navigation/
           │    └── main_navigation_scaffold_test.dart
           └── relay_management/
                ├── relay_management_screen_test.dart
                └── relay_management_controller_test.dart

Powered by TurnKey Linux.