MovieMapper/docs/iOS_PLAN.md
Jarian Cottingham 2ce7ab14c9 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

11 KiB

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

# 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/)

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
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
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
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
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
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

# 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

# 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

# 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

# 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

# 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

# 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

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

# 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

#!/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-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