This document explains the app architecture in plain language first, then maps that explanation to the Swift files.
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.
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.
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]
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
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
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
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
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
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
DeviceManager Is CentralWithout 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.
A useful way to read this codebase is to ask, “Who owns this piece of state?”
DeviceManager owns shared app/device state.MediaGridView owns temporary interaction state.MediaFile owns per-item presentation state such as thumbnail image and loading flag.MediaFile Wraps ICCameraFileICCameraFile comes from the system framework. It is not designed around SwiftUI’s observation system.
The wrapper provides:
The Android stack mirrors the iPhone stack’s ownership model:
AndroidDeviceManager owns shared device and file state.AndroidMediaGridView and AndroidFileBrowserView own temporary interaction state (selection, sort, filter).AndroidMediaFile owns per-item presentation state (thumbnail, loading flag).MTPBridge owns the subprocess lifecycle.RangeFilter and the sort enums are shared between both stacks because they operate on a RangeFilterMatchable protocol rather than on a concrete file type.
If you are new to Swift or SwiftUI, read the iPhone path first:
LameApp.swiftContentView.swiftMediaFile.swiftDeviceManager.swiftMediaGridView.swiftMediaItemView.swiftSortOptions.swift and SizeRangeFilter.swiftOnce you understand the iPhone path, the Android path follows the same pattern:
MTPBridge.swiftAndroidMediaFile.swiftAndroidDeviceManager.swiftAndroidMediaGridView.swiftAndroidFileBrowserView.swiftFor a thorough explanation of the approach, tradeoffs, and limitations, read docs/Android-Support.md.