MovieMapper/docs/iOS_PLAN.md
Jarian Cottingham 1a35eb346e chore: reorganize repo layout, remove dead files
- Move phase/plan docs into docs/
- Move legacy node:test files into tests/legacy/ with README
- Remove .backup file, test audit artifacts, and unused AI prompt/skill files
- Remove broken iOS GitHub workflows (reference missing MovieMapper-iOS/)
2026-08-20 20:11:57 +00:00

401 lines
11 KiB
Markdown

# MovieMapper iOS Implementation Plan
## Architecture Overview
Since you want **SwiftUI with Swift only** (no Rust backend) and **offline-first**, this will be a complete rewrite in Swift using Apple frameworks.
---
## Phase 1: Project Setup & Foundation
### 1.1 Create Xcode Project
```bash
# Create new iOS App project (iPad only)
xcodebuild -createProject MovieMapper -template iOS-App -destination ./MovieMapper-iOS
# Or using Swift Package Manager for more control
mkdir MovieMapper-iOS && cd MovieMapper-iOS
swift package init --type executable --name MovieMapper-iOS
```
### 1.2 Directory Structure
```
MovieMapper-iOS/
├── MovieMapper-iOS/ # Main app target
│ ├── ContentView.swift
│ ├── MovieMapperApp.swift
│ └── ...
├── SharedModels/ # Shared data models
│ ├── MediaFile.swift
│ ├── Show.swift
│ ├── Season.swift
│ ├── Episode.swift
│ └── TaggedFile.swift
├── Services/ # Business logic
│ ├── FileScanner.swift
│ ├── MetadataExtractor.swift
│ ├── TVDBClient.swift
│ ├── FileMapper.swift
│ └── AuditLogger.swift
├── UI/ # SwiftUI views
│ ├── BrowseView.swift
│ ├── SearchView.swift
│ ├── FileListView.swift
│ ├── SeasonDetailView.swift
│ └── ModalViews/
├── Utils/ # Helper functions
│ ├── Filesystem.swift
│ ├── FFmpegWrapper.swift
│ └── DateFormatters.swift
└── Resources/ # Assets
├── Assets.xcassets
└── Localizable.strings
```
---
## Phase 2: Core Implementation
### 2.1 Data Models (SharedModels/)
```swift
struct MediaFile: Identifiable, Codable, Hashable {
let id: UUID
let name: String
let path: String
let size: Int64
let modified: Date
let duration: String // "mm:ss" format
let quality: String // "1080p", "4K", etc.
let fps: String // "24fps", "30fps", etc.
var isFolder: Bool
var tags: [TagType]
}
enum TagType: String, Codable, CaseIterable {
case extra = "extra"
case behindTheScenes = "behind-the-scenes"
case delete = "delete"
}
```
### 2.2 File Scanner Service
- **Framework**: Use `FileManager` with `URLQueryItem` for directory access
- **Permissions**: Request `NSPhotoLibraryAddUsageDescription` for file access
- **Non-recursive scanning**: Scan only current directory (match desktop behavior)
- **Progress updates**: Use Combine/Publishers for real-time UI updates
```swift
class FileScanner {
func scanDirectory(at path: URL, progress: @escaping (Int, Int, String) -> Void) async throws -> [MediaFile]
}
```
### 2.3 Metadata Extractor
- **FFmpeg integration**: Use `ffmpeg-kit` or `SwiftFFmpeg` package
- **Extract**: duration, resolution, FPS
- **Error handling**: Gracefully handle corrupted files
```swift
class MetadataExtractor {
func extractDuration(from url: URL) async throws -> String
func extractQuality(from url: URL) async throws -> (quality: String, fps: String)
}
```
### 2.4 TVDB Client
- **API**: TheTVDB v4 REST API
- **Authentication**: Bearer token with caching
- **Features**: Search shows, get details, fetch episodes
- **Offline caching**: Store recent searches in `UserDefaults` or Core Data
```swift
class TVDBClient {
func authenticate() async throws
func search(query: String) async throws -> [Show]
func getShowDetails(id: Int) async throws -> ShowDetails
func getSeasonEpisodes(showId: Int, seasonNumber: Int) async throws -> [Episode]
}
```
### 2.5 File Mapper
- **Jellyfin naming**: `ShowName S01E01 - quality.ext`
- **Episode ranges**: Support `S01E01-E03` format
- **Folder creation**: Create `extras/`, `behind the scenes/`, `commentary/` directories
```swift
class FileMapper {
func mapFiles(_ files: [MediaFile], to show: Show, season: Season) async throws -> MappingResult
}
```
### 2.6 Audit Logger
- **Format**: JSON lines in `.audit` files
- **Storage**: Write to same directory as files
- **Content**: Timestamp, action, details
```swift
class AuditLogger {
func log(action: String, details: [String: Any], in directory: URL) async throws
}
```
---
## Phase 3: UI Implementation
### 3.1 Main Views
#### BrowseView.swift
- Directory picker (UIDocumentPickerViewController)
- Breadcrumb navigation (iPad multi-column)
- File list with:
- Folder icons 📁
- Media file details (duration, quality, FPS)
- Tag icons (clickable)
- Play button (moves tagged files)
#### SearchView.swift
- Search bar for TVDB
- Show results with thumbnails
- Season selection with episode lists
- Episode count badges
#### FileListView.swift
- Drag-and-drop reordering (iOS 17+)
- Episode number editing
- Tag toggling (extra, behind-the-scenes, delete)
- Floating action button for moving all tagged files
### 3.2 iPad-Specific Features
- **Multi-column navigation**: Use `NavigationSplitView` for sidebar + content
- **Split view**: Search on left, files on right
- **Large screen optimization**: Use more horizontal space
### 3.3 Modal Views
- Video preview modal
- Confirmation dialogs for destructive actions
- Loading indicators for async operations
---
## Phase 4: Testing Strategy
### 4.1 Command Line Testing Tools
#### 4.1.1 File Scanner Tests
```bash
# Create test directory structure
mkdir -p /tmp/moviemapper_test/{Season\ 01,Season\ 02}
touch /tmp/moviemapper_test/Season\ 01/episode1.mp4
touch /tmp/moviemapper_test/Season\ 01/episode2.mkv
# Run scanner test
swift test --filter FileScannerTests/testScanDirectory
```
#### 4.1.2 Metadata Extraction Tests
```bash
# Create test video file (using ffmpeg)
ffmpeg -f lavfi -i testsrc=duration=5:size=1920x1080:rate=30 /tmp/test_video.mp4
# Run metadata extraction test
swift test --filter MetadataExtractorTests/testExtractDuration
swift test --filter MetadataExtractorTests/testExtractQuality
```
#### 4.1.3 TVDB API Tests
```bash
# Set API key (from .env file)
export TVDB_API_KEY="your-api-key-here"
# Run TVDB integration tests
swift test --filter TVDBClientTests/testAuthenticate
swift test --filter TVDBClientTests/testSearchShows
swift test --filter TVDBClientTests/testGetShowDetails
```
#### 4.1.4 File Mapping Tests
```bash
# Create test files
mkdir -p /tmp/moviemapper_test/Season\ 01
for i in {1..5}; do
ffmpeg -f lavfi -i testsrc=duration=1:size=1280x720:rate=24 \
/tmp/moviemapper_test/Season\ 01/video_${i}.mp4
done
# Run mapping test
swift test --filter FileMapperTests/testMapSingleEpisode
swift test --filter FileMapperTests/testMapEpisodeRange
```
### 4.2 UI Testing
```bash
# Run UI tests
xcodebuild test -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-destination 'platform=iOS Simulator,name=iPad Pro (12.9-inch) (17th generation)' \
-destination-timeout 60
```
### 4.3 Performance Testing
```bash
# Test scanning performance with many files
xcodebuild test -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOSPerformance \
-destination 'platform=iOS Simulator,name=iPad Pro' \
-enableCodeCoverage YES
```
---
## Phase 5: Advanced Features
### 5.1 Offline-First Architecture
- **Local caching**: Store show search results in `UserDefaults` or `CoreData`
- **File scanning**: Work completely offline
- **TVDB features**: Optional, only when internet available
### 5.2 File System Access
- **Document Picker**: Let user select media library location
- **Security scoped bookmarks**: Persist access across app launches
- **iCloud integration**: Optional (for backup)
### 5.3 Tagging System
```swift
struct TagManager {
func addTag(_ tag: TagType, to file: MediaFile, in directory: URL) async throws
func removeTag(_ tag: TagType, from file: MediaFile, in directory: URL) async throws
func moveTaggedFiles(_ files: [MediaFile], to folder: String) async throws
}
```
---
## Phase 6: Build & Distribution
### 6.1 Build Commands
```bash
# Build for simulator
xcodebuild -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-configuration Debug \
-sdk iphonesimulator
# Build for device
xcodebuild -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-configuration Release \
-sdk iphoneos \
-archivePath MovieMapper-iOS.xcarchive \
archive
# Export IPA
xcodebuild -exportArchive \
-archivePath MovieMapper-iOS.xcarchive \
-exportOptionsPlist ExportOptions.plist
```
### 6.2 Testing Automation
```bash
#!/bin/bash
# run-tests.sh
# Set up test environment
export TVDB_API_KEY=$(cat .env | grep TVDB_API_KEY | cut -d'=' -f2)
# Run unit tests
swift test
# Run UI tests
xcodebuild test -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-destination 'platform=iOS Simulator,name=iPad Pro'
# Generate test report
xcodebuild -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-destination 'platform=iOS Simulator,name=iPad Pro' \
-quiet \
-testSummaryReport /tmp/test-results.xml
```
---
## Phase 7: Dependencies & Packages
### 7.1 Swift Package Manager Dependencies
```swift
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MovieMapper-iOS",
platforms: [.iOS(.v17)],
dependencies: [
.package(url: "https://github.com/ffmpeg-kit/swift.git", from: "6.0.0"),
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
.package(url: "https://github.com/rnapier/ReactiveSwift.git", from: "7.0.0")
],
targets: [
.target(
name: "MovieMapper-iOS",
dependencies: [
.product(name: "FFmpegKitSwift", package: "swift-ffmpeg-kit"),
.product(name: "Alamofire", package: "Alamofire"),
.product(name: "ReactiveSwift", package: "ReactiveSwift")
]
)
]
)
```
---
## Summary of Key Decisions
### Architecture
- **Pure SwiftUI** with Swift only (no Rust backend as requested)
- **Combine** for async operations and state management
- **Structured concurrency** (`async/await`) for all network/file operations
### Offline-First Design
- File scanning works completely offline
- TVDB features are optional (require internet)
- Local caching of recent searches and show details
### iPad-Specific Optimizations
- Multi-column navigation with `NavigationSplitView`
- Large screen layout with sidebar + content
- Touch-friendly UI elements (minimum 44pt tap targets)
### Testing Approach
- **Command line tests**: Unit tests for all services
- **UI tests**: Simulator-based automation
- **Manual testing**: Test on actual iPad devices
---
## Implementation Timeline
| Week | Task |
|------|------|
| 1-2 | Project setup, data models, basic file scanner |
| 3-4 | Metadata extractor, UI scaffolding |
| 5-6 | TVDB client, search functionality |
| 7-8 | File mapping, tagging system |
| 9 | Testing (unit + UI), bug fixes |
| 10 | iPad optimization, polishing |
---
## Next Steps
1. **Create Xcode project** using the structure above
2. **Implement core data models** (MediaFile, Show, Season, Episode)
3. **Build FileScanner service** with progress reporting
4. **Create SwiftUI views** for directory browsing
5. **Implement metadata extraction** with FFmpeg
6. **Add TVDB integration** for show search
7. **Build tagging and file movement** features
8. **Write comprehensive tests** for all services
9. **Optimize for iPad** with split views and large screen layouts
10. **Test on physical iPad device** before App Store submission