Skip to content

Project-Delilah/delilah_ai

Repository files navigation

Delilah - AI Image Generation & Virtual Try-On App

Delilah Logo

Built with Flutter Riverpod GoRouter Gal Cloudinary


πŸ“± Screenshots

Login Screen Home Screen Gallery View
Image Generation Virtual Try-On Virtual Try-On Output

Overview

Delilah is a production-ready Flutter mobile application that provides AI-powered image generation and editing capabilities. The app connects to a FastAPI backend with Vertex AI for image processing and uses Cloudinary for image storage.

Features

Feature Endpoint Description
Image Generation /api/generate Generate stunning images from text prompts using Vertex AI Imagen
Virtual Try-On /api/tryon Try on clothes virtually by combining person photos with clothing items
Image Editing /api/edit Apply filters and semantic modifications to existing images
Image Upscale /api/upscale Enhance resolution and remove digital noise from images
Product Makeover /api/product-makeover Place products in contextual synthetic backgrounds
Fix Old Image /api/fixoldimage Restore old/damaged photos by removing artifacts
Gallery /api/gallery View, save, and manage all generated images
Wallpaper - Set any image as device wallpaper

Architecture

Technology Stack

  • Frontend: Flutter 3.11+ with Dart
  • State Management: Riverpod 2.5+
  • Routing: GoRouter 14+
  • HTTP Client: Dio
  • Image Storage: Cloudinary (unsigned uploads)
  • Secure Storage: flutter_secure_storage (JWT tokens)
  • Image Saving: Gal (gallery access)
  • Image Picking: image_picker

Project Structure

lib/
β”œβ”€β”€ main.dart                    # App entry point
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ config.dart              # API URLs and Cloudinary config
β”‚   β”œβ”€β”€ router.dart              # GoRouter configuration with navigation
β”‚   β”œβ”€β”€ network/
β”‚   β”‚   └── pb_interceptor.dart   # HTTP interceptors
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ cloudinary_service.dart   # Cloudinary upload with caching
β”‚   β”‚   └── secure_storage.dart       # Token persistence
β”‚   └── theme/
β”‚       β”œβ”€β”€ theme_controller.dart     # Design tokens (colors, spacing)
β”‚       └── glass_theme.dart          # Theme configuration
β”œβ”€β”€ features/
β”‚   β”œβ”€β”€ auth/                    # Authentication
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   └── auth_repository.dart  # Login/register API calls
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”‚   β”œβ”€β”€ login_screen.dart
β”‚   β”‚   β”‚   └── register_screen.dart
β”‚   β”‚   └── providers/
β”‚   β”‚       └── auth_provider.dart    # Auth state management
β”‚   β”œβ”€β”€ home/
β”‚   β”‚   └── presentation/
β”‚   β”‚       └── home_screen.dart      # Hero section + quick actions
β”‚   β”œβ”€β”€ endpoints/
β”‚   β”‚   β”œβ”€β”€ image_generation/   # Generate images from prompts
β”‚   β”‚   β”œβ”€β”€ virtual_tryon/       # Virtual clothing try-on
β”‚   β”‚   β”œβ”€β”€ image_edit/         # Image editing & filters
β”‚   β”‚   β”œβ”€β”€ upscale/            # Image upscaling
β”‚   β”‚   β”œβ”€β”€ product_makeover/   # Product background change
β”‚   β”‚   └── fix_old_image/      # Photo restoration
β”‚   └── history/
β”‚       β”œβ”€β”€ presentation/
β”‚       β”‚   └── history_screen.dart  # Gallery with save/wallpaper
β”‚       └── providers/
β”‚           └── history_provider.dart # Gallery API state
└── shared/
    β”œβ”€β”€ widgets/
    β”‚   β”œβ”€β”€ glass_button.dart   # Custom styled button
    β”‚   β”œβ”€β”€ glass_input.dart    # Custom styled input
    β”‚   └── async_value_widget.dart
    └── utils/
        └── wallpaper_engine.dart    # Android wallpaper setter

State Management Pattern

The app uses Riverpod with a consistent pattern across all features:

  1. Provider: Defines the state class (immutable data class)
  2. Notifier: Handles state mutations and async operations
  3. Repository: Makes API calls with proper auth headers
  4. Screen: Consumes state via ref.watch() and triggers actions via ref.read()

Example pattern:

// State class
class TryOnState {
  final File? personImage;
  final String? resultUrl;
  final bool isGenerating;
  // ...
}

// Notifier
class TryOnNotifier extends Notifier<TryOnState> {
  Future<void> generate() async {
    state = state.copyWith(isGenerating: true);
    // API call...
    state = state.copyWith(resultUrl: result, isGenerating: false);
  }
}

// Provider
final tryOnNotifierProvider = NotifierProvider<TryOnNotifier, TryOnState>(() => TryOnNotifier());

Backend API

Authentication

The app uses JWT tokens from PocketBase for authentication.

Login: POST /api/auth/login

{"email": "user@example.com", "password": "password"}

Response:

{"token": "jwt_token_here"}

Image Endpoints

All endpoints (except login/register) require Authorization: Bearer <token> header.

Method Endpoint Request Body Response
POST /api/generate {"prompt": "..."} {"status": "success", "secure_url": "..."}
POST /api/tryon {"person_image_url": "...", "product_image_url": "..."} {"status": "success", "secure_url": "..."}
POST /api/edit {"image_url": "...", "edit_prompt": "..."} {"status": "success", "secure_url": "..."}
POST /api/upscale {"image_url": "...", "enhancement_focus": "..."} {"status": "success", "secure_url": "..."}
POST /api/product-makeover {"product_image_url": "...", "background_context": "..."} {"status": "success", "secure_url": "..."}
POST /api/fixoldimage {"image_url": "...", "repair_instructions": "..."} {"status": "success", "secure_url": "..."}
GET /api/gallery - {"images": [{"secure_url": "...", "public_id": "...", ...}]}
DELETE /api/gallery {"public_id": "..."} {"success": true}

Cloudinary Integration

Images are uploaded to Cloudinary with folder structure:

delilah_users/<email_cleaned>/<tag>/

Example: delilah_users/user_example_com/generated_studio/


Design System

Colors

Color Hex Usage
canvasWhite #FFFFFF Main background
cohereBlack #1A1A1A Primary text
actionBlue #4F46E5 Primary actions
softStone #F3F4F6 Card backgrounds
hairline #E5E7EB Borders
mutedSlate #6B7280 Secondary text
deepEnterpriseGreen #10B981 Success states
errorRed #EF4444 Error states

Typography

Uses Unica77 font family (via design tokens in theme_controller.dart):

  • headlineLarge: 28px, bold
  • titleMedium: 18px, semibold
  • bodyMedium: 14px, regular
  • bodySmall: 12px, regular

Navigation

The app uses a Shell Route pattern with bottom navigation:

Home (default landing)
  └── Quick actions β†’ Generate, Try-On, Edit, Gallery
  
Tools (modal menu)
  β”œβ”€β”€ Generate Image
  β”œβ”€β”€ Virtual Try-On
  β”œβ”€β”€ Edit Image
  β”œβ”€β”€ Upscale
  β”œβ”€β”€ Product Makeover
  └── Fix Old Image
  
Gallery
  └── Grid view β†’ Detail sheet β†’ Save/Wallpaper/Delete
  
Logout β†’ Login screen

Navigation Logic

  • Unauthenticated users are redirected to /login
  • Authenticated users on auth pages are redirected to /home
  • Back gesture from any tool goes to Home (not exit app)
  • Gallery auto-refreshes on first load only

Building & Running

Prerequisites

  • Flutter 3.11+
  • Dart 3.11+
  • Android SDK with API 21+

Development

# Get dependencies
flutter pub get

# Run on connected device/emulator
flutter run

# Analyze code
flutter analyze

# Build debug APK
flutter build apk --debug

# Build release APK
flutter build apk --release

signature key

android/key.properties

storePassword=
keyPassword=
keyAlias=upload
storeFile=../../keystore/release-key.jks

Release Build

The release build uses a custom keystore located at ../keystore/release-key.jks.

# Build release (signs with release key)
flutter build apk --release

Permissions

Android (AndroidManifest.xml)

  • INTERNET - API calls
  • SET_WALLPAPER - Set wallpaper feature
  • READ_EXTERNAL_STORAGE (API ≀ 32) - Gallery access
  • WRITE_EXTERNAL_STORAGE (API ≀ 29) - Save to gallery

Key Implementation Details

Cloudinary Upload with Caching

Images uploaded from device are cached by file path to avoid re-uploading the same image:

final cachedUrl = cloudinary.getCachedUrl(file.path);
if (cachedUrl != null) {
  state = state.copyWith(imageUrl: cachedUrl);
  return;
}

Save to Gallery

Uses gal package for modern gallery access:

final tempDir = await getTemporaryDirectory();
final tempFile = File('${tempDir.path}/image.jpg');
await tempFile.writeAsBytes(response.bodyBytes);
await Gal.putImage(tempFile.path);
await tempFile.delete();

Wallpaper Setting (Android)

Uses platform channel to set wallpaper with center-crop for proper aspect ratio:

// android/app/src/main/kotlin/.../MainActivity.kt
val wallpaperManager = WallpaperManager.getInstance(applicationContext)
wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_SYSTEM)

Version History

Version Changes
1.0.8 Added launcher icons, assets folder
1.0.6 Fixed save image permission error
1.0.5 Add save/fullscreen to new endpoints, back gesture fix, gallery auto-refresh
1.0.4 Fix layout overflow in home screen modal
1.0.3 Add local image picker + Cloudinary upload, create homepage
1.0.2 Add missing API endpoints (edit, upscale, product-makeover, fixoldimage)
1.0.1 Add gallery delete functionality
1.0.0 Initial release with auth, generate, tryon, gallery

License

MIT License


Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make changes and test
  4. Run flutter analyze to check for issues
  5. Commit with clear description
  6. Push and create pull request