lame

Architecture

This document explains the app architecture in plain language first, then maps that explanation to the Swift files.

One-Sentence Description

The app is a thin SwiftUI desktop interface that connects to USB-attached phones — iPhones via Apple’s ImageCaptureCore framework, and Android phones via the libmtp command-line tools.

Architectural Goal

The main design goal is to isolate each device protocol from the modern UI layer.

Instead of letting views talk directly to ImageCaptureCore or subprocess tools, the project routes all device activity through one shared manager object per device type. Each manager turns low-level callbacks or subprocess output into simple observable state that SwiftUI can read directly.

Layer Diagram

flowchart TB
    subgraph iPhone path
        A[iPhone over USB] --> B[ImageCaptureCore]
        B --> C[DeviceManager]
        C --> D[MediaFile / FileSystemItem]
    end

    subgraph Android path
        E[Android over USB] --> F[libmtp tools]
        F --> G[MTPBridge actor]
        G --> H[AndroidDeviceManager]
        H --> I[AndroidMediaFile]
    end

    D --> J[ContentView]
    I --> J
    J --> K[MediaGridView / FileBrowserView]
    J --> L[AndroidMediaGridView / AndroidFileBrowserView]

Runtime Responsibility Diagram

flowchart LR
    subgraph App Shell
        A[LameApp]
        B[ContentView]
    end

    subgraph iPhone Integration
        C[DeviceManager]
        G[MediaFile]
    end

    subgraph Android Integration
        M[AndroidDeviceManager]
        N[MTPBridge]
        O[AndroidMediaFile]
    end

    subgraph iPhone Views
        D[MediaGridView]
        E[MediaItemView]
    end

    subgraph Android Views
        P[AndroidMediaGridView]
        Q[AndroidFileBrowserView]
    end

    subgraph Shared Types
        F[RangeFilter / RangeFilterPanel]
        H[SortField / SortOrder]
        I[AppError]
    end

    A --> C
    A --> M
    A --> B
    B --> D
    B --> P
    C --> G
    M --> N
    M --> O
    G --> D
    O --> P
    D --> E
    D --> F
    P --> Q
    P --> F

App Startup Flow

sequenceDiagram
    participant User
    participant App as LameApp
    participant Manager as DeviceManager
    participant Browser as ICDeviceBrowser
    participant UI as ContentView

    User->>App: Launch app
    App->>Manager: Create shared DeviceManager
    Manager->>Browser: Start device browsing
    App->>UI: Inject manager into environment
    Browser-->>Manager: Device discovered
    Manager-->>UI: connectedDevices updated
    UI-->>User: Sidebar shows connected iPhone

Device Session Flow

sequenceDiagram
    participant User
    participant Sidebar as DeviceSidebarView
    participant Manager as DeviceManager
    participant Device as ICCameraDevice
    participant Grid as MediaGridView

    User->>Sidebar: Select device
    Sidebar->>Manager: selectDevice(device)
    Manager->>Manager: Cancel old thumbnail tasks
    Manager->>Device: requestOpenSession()
    Device-->>Manager: deviceDidBecomeReady
    Manager->>Manager: buildFileList(from:)
    Manager-->>Grid: mediaFiles updated
    Grid-->>User: Grid becomes visible

Thumbnail Flow

sequenceDiagram
    participant Grid as MediaGridView
    participant Cell as MediaItemView
    participant Manager as DeviceManager
    participant File as ICCameraFile

    Grid->>Cell: Render visible cell
    Cell->>Manager: onThumbnailNeeded()
    Manager->>Manager: Enforce concurrency cap
    Manager->>File: requestThumbnailData
    File-->>Manager: Data callback
    Manager->>Manager: Decode NSImage
    Manager-->>Cell: thumbnail updated via MediaFile

Deletion Flow

sequenceDiagram
    participant User
    participant Grid as MediaGridView
    participant Manager as DeviceManager
    participant Device as ICCameraDevice

    User->>Grid: Confirm delete
    Grid->>Manager: deleteFiles(selectedFiles)
    Manager->>Device: requestDeleteFiles
    Device-->>Manager: completion / failed items
    Manager->>Manager: Remove successful files from local state
    Manager-->>Grid: mediaFiles updated
    Grid-->>User: Deleted items disappear

Android Device Session Flow

sequenceDiagram
    participant User
    participant Sidebar as DeviceSidebarView
    participant ADM as AndroidDeviceManager
    participant Bridge as MTPBridge
    participant Grid as AndroidMediaGridView

    User->>Sidebar: Connect Android phone
    Sidebar->>ADM: poll() detects VID via ioreg
    ADM->>Bridge: detectDevice() → mtp-detect
    Bridge-->>ADM: MTPDeviceInfo (name, VID, PID)
    ADM-->>Sidebar: connectedDevice updated
    Sidebar-->>User: Android row appears
    User->>Sidebar: Click Android row
    Sidebar->>ADM: selectDevice()
    ADM->>Bridge: listFiles() → mtp-files
    Bridge-->>ADM: [MTPFileInfo] flat list
    ADM->>ADM: Build mediaFiles + fileSystemRoot
    ADM-->>Grid: mediaFiles updated
    Grid-->>User: Photo grid becomes visible

Why DeviceManager Is Central

Without DeviceManager, every view would need to understand:

That would make the UI harder to understand and much harder to evolve. By centralizing those concerns, the views can focus on rendering and local interaction state.

State Ownership

A useful way to read this codebase is to ask, “Who owns this piece of state?”

Why MediaFile Wraps ICCameraFile

ICCameraFile comes from the system framework. It is not designed around SwiftUI’s observation system.

The wrapper provides:

State Ownership — Android

The Android stack mirrors the iPhone stack’s ownership model:

RangeFilter and the sort enums are shared between both stacks because they operate on a RangeFilterMatchable protocol rather than on a concrete file type.

Reading Strategy For Newcomers

If you are new to Swift or SwiftUI, read the iPhone path first:

  1. LameApp.swift
  2. ContentView.swift
  3. MediaFile.swift
  4. DeviceManager.swift
  5. MediaGridView.swift
  6. MediaItemView.swift
  7. SortOptions.swift and SizeRangeFilter.swift

Once you understand the iPhone path, the Android path follows the same pattern:

  1. MTPBridge.swift
  2. AndroidMediaFile.swift
  3. AndroidDeviceManager.swift
  4. AndroidMediaGridView.swift
  5. AndroidFileBrowserView.swift

For a thorough explanation of the approach, tradeoffs, and limitations, read docs/Android-Support.md.