SelfieCam/AI_Implementation.md
Matt Bruce 255f3c2d68 Add branded SelfieCamTheme with visual improvements to settings
- Create SelfieCamTheme.swift with rose/magenta-tinted surface colors matching app branding
- Add App-prefixed typealiases (AppSurface, AppAccent, AppStatus, etc.) to avoid Bedrock conflicts
- Add SettingsCard container for visual grouping of related settings
- Update SettingsView with card containers and accent-colored section headers
- Update all settings-related files to use new theme colors
- Pro section uses warning color, Debug section uses error color for visual distinction
2026-01-04 16:54:23 -06:00

13 KiB

AI Implementation Guide

How This App Was Architected & Built

This project was developed following strict senior-level iOS engineering standards, with guidance from AI assistants acting as Senior iOS Engineers specializing in SwiftUI and modern Apple frameworks.


Guiding Principles (from AGENTS.md)

  • Protocol-Oriented Programming (POP) first: All shared capabilities defined via protocols before concrete types
  • MVVM-lite: Views are "dumb" — all logic lives in @Observable view models
  • Bedrock Design System: Centralized design tokens, no magic numbers
  • Full accessibility: Dynamic Type, VoiceOver labels/hints/traits/announcements
  • Modern Swift & SwiftUI: Swift 6 concurrency, @MainActor, foregroundStyle, clipShape(.rect), NavigationStack
  • Testable & reusable design: Protocols enable mocking and future package extraction

Architecture Overview

Shared/
├── DesignConstants.swift           → Uses Bedrock design tokens
├── BrandingConfig.swift            → App icon & launch screen config
├── Color+Extensions.swift          → Ring light color presets
├── Models/
│   ├── CameraFlashMode.swift       → Flash mode enum
│   ├── CameraHDRMode.swift         → HDR mode enum  
│   ├── PhotoQuality.swift          → Photo quality settings
│   └── CapturedPhoto.swift         → Photo data model
├── Protocols/
│   ├── RingLightConfigurable.swift → Border, color, brightness
│   ├── CaptureControlling.swift    → Timer, grid, zoom, capture
│   └── PremiumManaging.swift       → Subscription state
├── Premium/
│   └── PremiumManager.swift        → RevenueCat integration
├── Services/
│   └── PhotoLibraryService.swift   → Photo saving service
└── Storage/
    └── SyncedSettings.swift        → iCloud-synced settings

Features/
├── Camera/                         → Main camera UI
│   ├── ContentView.swift           → Screen coordinator
│   ├── Views/                      → UI components
│   └── GridOverlay.swift           → Rule of thirds
├── Settings/                       → Configuration
│   ├── SettingsView.swift          → Settings UI
│   └── SettingsViewModel.swift     → Settings logic + sync
└── Paywall/                        → Pro subscription flow

Key Implementation Decisions

1. Ring Light Effect

  • Achieved using RingLightOverlay view that creates a colored border around the camera preview
  • Border width controlled via user setting (10-120pt range)
  • Multiple preset colors with premium custom color picker
  • Adjustable opacity/brightness (10%-100%)
  • Enabled/disabled toggle for quick access

2. Camera System

  • Uses MijickCamera framework for SwiftUI-native camera handling
  • Supports front and back camera switching
  • Pinch-to-zoom with smooth interpolation
  • Flash modes: Off, On, Auto (with premium flash sync)
  • HDR mode support (premium feature)
  • Photo quality settings (medium free, high premium)

3. Capture Enhancements

  • Self-timer with countdown (3s free, 5s/10s premium)
  • Post-capture preview with share functionality
  • Auto-save option to Photo Library
  • Front flash using screen brightness
  • Camera Control button (iPhone 16+): Full press captures, light press locks focus/exposure
  • Hardware shutter: Volume buttons trigger capture via VolumeButtonObserver

4. Freemium Model

  • Built with RevenueCat for subscription management
  • PremiumManager wraps RevenueCat SDK
  • PremiumGate utility for clean premium feature access
  • Settings automatically fall back to free defaults when not premium

5. iCloud Sync

  • Uses Bedrock's CloudSyncManager for settings synchronization
  • SyncedSettings model contains all user preferences
  • Debounced saves for slider values (300ms delay)
  • Real-time sync status display in Settings
  • Available to all users (not a premium feature)

6. Branding System

  • Uses Bedrock's Branding module for launch screen and app icon
  • BrandingConfig.swift defines app-specific colors and symbols
  • LaunchBackground.colorset matches launch screen primary color
  • Animated launch with configurable duration and pattern style
  • Icon generator available in DEBUG builds

Camera Control Button Integration

Overview

The app supports the Camera Control button on iPhone 16+ via AVCaptureEventInteraction (iOS 17.2+).

Files Involved

File Purpose
Shared/Protocols/CaptureEventHandling.swift Protocol defining hardware capture event handling
Features/Camera/Views/CaptureEventInteraction.swift AVCaptureEventInteraction wrapper and SwiftUI integration
Features/Camera/Views/VolumeButtonObserver.swift Volume button capture support (legacy)

Supported Hardware Events

Event Hardware Action
Primary (full press) Camera Control, Action Button Capture photo
Secondary (light press) Camera Control Lock focus/exposure
Volume buttons All iPhones Capture photo

Implementation Details

// CaptureEventInteractionView is added to the camera ZStack
CaptureEventInteractionView(
    onCapture: { performCapture() },
    onFocusLock: { locked in handleFocusLock(locked) }
)

// The interaction uses AVCaptureEventInteraction (iOS 17.2+)
AVCaptureEventInteraction(
    primaryEventHandler: { phase in /* capture on .ended */ },
    secondaryEventHandler: { phase in /* focus lock on .began/.ended */ }
)

Device Compatibility

  • iPhone 16+: Full Camera Control button support (press + light press)
  • iPhone 15 Pro+: Action button support (when configured for camera)
  • All iPhones: Volume button shutter via VolumeButtonObserver

Premium Feature Implementation

How Premium Gating Works

The app uses a centralized PremiumGate utility for consistent premium feature handling:

// In SettingsViewModel
var isMirrorFlipped: Bool {
    get { PremiumGate.get(cloudSync.data.isMirrorFlipped, default: false, isPremium: isPremiumUnlocked) }
    set { 
        guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
        updateSettings { $0.isMirrorFlipped = newValue } 
    }
}

Premium Features List

Feature Free Value Premium Value
Ring light colors Pure White, Warm Cream All presets + custom
Timer options Off, 3s Off, 3s, 5s, 10s
Photo quality Medium Medium, High
HDR mode Off Off, On, Auto
True mirror Off Configurable
Skin smoothing Off Configurable
Flash sync Off Configurable
Center stage Off Configurable

Settings & Persistence

SyncedSettings Model

All user preferences are stored in a single SyncedSettings struct that syncs via iCloud:

  • Ring light: size, color ID, custom color RGB, opacity, enabled
  • Camera: position, flash mode, HDR mode, photo quality
  • Display: mirror flip, skin smoothing, grid visible
  • Capture: timer, capture mode, auto-save
  • Premium features: flash sync, center stage

Debounced Saves

Slider values (ring size, opacity) use debounced saving to prevent excessive iCloud writes:

private func debouncedSave(key: String, action: @escaping () -> Void) {
    debounceTask?.cancel()
    debounceTask = Task {
        try? await Task.sleep(for: .milliseconds(300))
        guard !Task.isCancelled else { return }
        action()
    }
}

Branding Implementation

Files Involved

  1. BrandingConfig.swift - Defines app icon and launch screen configurations
  2. LaunchBackground.colorset - Asset catalog color matching primary brand color
  3. SelfieCamApp.swift - Wraps ContentView with AppLaunchView

Color Scheme

extension Color {
    enum Branding {
        static let primary = Color(red: 0.85, green: 0.25, blue: 0.45)   // Vibrant magenta
        static let secondary = Color(red: 0.45, green: 0.12, blue: 0.35) // Deep purple
        static let accent = Color.white
    }
}

Launch Screen Configuration

static let selfieCam = LaunchScreenConfig(
    title: "SELFIE CAM",
    tagline: "Look Your Best",
    iconSymbols: ["camera.fill", "sparkles"],
    cornerSymbol: "sparkle",
    patternStyle: .radial,
    // ... colors and sizing
)

Development Workflow

Adding a New Feature

  1. Define the protocol (if shared behavior)
  2. Add to SyncedSettings (if needs persistence)
  3. Implement in SettingsViewModel (with premium gating if applicable)
  4. Add UI in SettingsView
  5. Update documentation (README, this file)

Adding a Premium Feature

  1. Add setting to SyncedSettings with appropriate default
  2. Use PremiumGate.get() for the getter with free default
  3. Use PremiumGate.canSet() guard for the setter
  4. Add premium indicator (crown icon) in UI
  5. Wire up paywall trigger for non-premium users

Testing Premium Features

Set environment variable in scheme:

  • Name: ENABLE_DEBUG_PREMIUM
  • Value: 1

Reusability & Extraction

The codebase is structured for future extraction into reusable packages:

Potential Package Contents
SelfieCameraKit Camera views, capture logic, preview components
RingLightKit Ring light overlay, color presets, configuration
PremiumKit Premium manager, gating utilities, paywall
SyncedSettingsKit CloudSyncManager, settings model pattern

Key Dependencies

Dependency Purpose Integration
Bedrock Design system, branding, cloud sync Local Swift package
MijickCamera Camera capture and preview SPM dependency
RevenueCat Subscription management SPM dependency

Code Quality Standards

  • No magic numbers: All values from Design constants
  • Full accessibility: Every interactive element has VoiceOver support
  • Protocol-first: Shared behavior defined via protocols
  • Separation of concerns: Views are dumb, ViewModels contain logic
  • Modern APIs: Swift 6, async/await, @Observable
  • Documentation: Code comments, README, implementation guides

Known Issues / TODO

Camera Control Button Light Press - NOT WORKING

Status: Broken - Needs Investigation

The Camera Control button (iPhone 16+) full press works for photo capture, but the light press (secondary action) does NOT work.

Testing revealed that the "secondary" events in logs were actually triggered by volume button, not Camera Control light press. The volume button works because onCameraCaptureEvent handles all hardware capture buttons.

What Works:

  • Camera Control full press → triggers photo capture
  • Volume up/down → triggers secondary event (focus lock)

What Doesn't Work:

  • Camera Control light press → no event received at all
  • Camera Control swipe gestures (zoom) → Apple-exclusive API

User Action Required - Check Accessibility Settings:

Settings > Accessibility > Camera Control:

  • Ensure Camera Control is enabled
  • Ensure Light-Press is turned ON
  • Adjust Light-Press Force if needed
  • Check Double Light-Press Speed

These system settings may affect third-party apps differently than Apple Camera.

Investigation Areas:

  1. Accessibility settings may block third-party light press

    • User reports light press works in Apple Camera but not SelfieCam
    • System may require explicit light-press enablement per-app
  2. MijickCamera session configuration

    • The third-party camera framework may interfere with light press detection
    • MijickCamera manages its own AVCaptureSession - may conflict
    • Try testing with raw AVCaptureSession to isolate the issue
  3. onCameraCaptureEvent secondaryAction limitations

    • The secondaryAction closure receives volume button events correctly
    • Camera Control light press may use different event pathway
    • Apple may internally route light press to their Camera app exclusively
  4. Light press may require AVCapturePhotoOutput configuration

    • Secondary events might need specific photo output settings
    • Check if AVCapturePhotoSettings has light-press related properties
  5. Possible Apple restriction (most likely)

    • Light press and swipe gestures appear restricted to first-party apps
    • Similar to swipe-to-zoom which is Apple-exclusive
    • No public API documentation confirms light press availability

Future Enhancements

Potential areas for expansion:

  • Real-time filters (beauty, color grading)
  • Gesture-based capture (smile detection)
  • Widget for quick camera access
  • Apple Watch remote trigger
  • Export presets (aspect ratios, watermarks)
  • Social sharing integrations
  • Camera Control button swipe-to-zoom (if Apple makes API public)

This architecture demonstrates production-quality SwiftUI development while delivering a polished, competitive user experience.