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

375 lines
13 KiB
Markdown

# 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
```swift
// 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:
```swift
// 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:
```swift
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
```swift
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
```swift
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.