Odak — Complete SwiftUI Rebuild Plan

by adminarchitectureiosodakswiftui

Odak — Complete SwiftUI Rebuild Plan

Context

The Odak app is currently built with React Native/Expo (Expo 55, RN 0.83, Reanimated, Unistyles v3, Skia). The user wants a ground-up rewrite in pure SwiftUI — no React Native, no JavaScript. The existing app has a mature feature set: timer state machine, 25 achievements, dot grid visualizations, Live Activities, widgets, 9 accent colors, and rich animations. The existing Swift code for widgets and Live Activities can be reused directly.

Goal: A native SwiftUI app that is feature-complete with the current React Native version, sharing code between app, widget, and Live Activity targets via a local Swift package.


Skills Required

| Skill Area | What It Covers | |---|---| | SwiftUI (advanced) | Canvas, TimelineView, NavigationStack, TabView, sheet detents, GeometryReader, @Observable, @Environment, @AppStorage, preferredColorScheme | | Swift Concurrency | async/await, Sendable types, MainActor isolation, Task for fire-and-forget side effects | | Core Graphics / Canvas | Drawing 50–4000 dots per frame, 8 custom shapes (stars, hearts, hexagons, diamonds), hit testing | | Gesture System | DragGesture for pan/hold-to-start, LongPressGesture, simultaneous gestures, haptic feedback | | Animation | withAnimation springs, TimelineView for 60fps Canvas, breathing loops, staggered transitions, parallax scroll | | GRDB.swift | SQLite wrapper for sessions, achievements, stats — generated columns, views, migrations, WAL concurrent access | | UserDefaults + App Groups | Settings storage, widget data sharing, @AppStorage with suite name | | ActivityKit | Live Activities, Dynamic Island (existing Swift code to adapt) | | WidgetKit | Home screen widgets with AppIntent configuration (existing Swift code to adapt) | | Swift Charts | Bar charts (7/30/90 day), heatmaps for session visualization | | ImageRenderer | Share card snapshot capture (replaces ViewShot) | | PhotosUI | PHPickerViewController for event image selection | | iOS 26 Liquid Glass | .glassEffect(.regular) with #if compiler(>=6.2) + #available(iOS 26.0, *) fallback chain | | SPM Local Packages | Shared OdakKit package between main app, widget, and Live Activity targets | | Xcode Project Setup | Multi-target project with app, widget extension, Live Activity extension, shared package |


Architecture

Project Structure

Odak/
├── Odak.xcodeproj
├── OdakApp/                          # Main app target
│   ├── App/
│   │   ├── OdakApp.swift             # @main, WindowGroup
│   │   └── AppState.swift            # Root @Observable container
│   ├── Navigation/
│   │   └── AppTabView.swift          # 4-tab TabView + coordinators
│   ├── Features/
│   │   ├── Focus/                    # Timer screen (Tab 1)
│   │   ├── Time/                     # Time visualization views
│   │   ├── Dates/                    # Countdown/milestone events (Tab 2)
│   │   ├── Stats/                    # Charts & history (Tab 3)
│   │   ├── You/                      # Profile, achievements (Tab 4)
│   │   └── Modals/                   # Settings, Share, Welcome
│   ├── Components/                   # Shared UI: AdaptiveCard, DotShapes, etc.
│   └── Utils/                        # HapticManager, Capabilities
├── OdakKit/                          # SPM local package (shared)
│   └── Sources/OdakKit/
│       ├── Domain/                   # TimerEngine, Presets, AchievementDefs
│       ├── Models/                   # All Codable structs
│       ├── Data/                     # OdakDatabase (GRDB), AppGroupStorage, ImageStorage
│       └── Theme/                    # AccentColor, BackgroundMode, DotShape enums
├── OdakWidget/                       # Widget extension (port existing Swift)
├── OdakLiveActivity/                 # Live Activity extension (port existing Swift)
└── OdakIntents/                      # AppIntents for widget configuration

Key Decisions

| Decision | Choice | Why | |---|---|---| | Min deployment | iOS 17.0 | Required for @Observable, modern NavigationStack, Swift Charts, AppIntents | | Persistence (structured) | GRDB.swift | Existing schema uses generated columns, complex views, ON CONFLICT upserts — SwiftData can't express these | | Persistence (settings) | UserDefaults (App Group suite) | Widgets already read from this; @AppStorage provides SwiftUI reactivity | | State management | @Observable + @Environment | Per-property tracking, no third-party deps | | Dot grid rendering | Canvas + TimelineView | Single draw call for 50–4000 dots; individual views would tank performance | | Charts | Swift Charts | Native, accessible, matches iOS design language | | Code sharing | SPM local package (OdakKit) | Clean sharing between 3 targets without framework embedding issues | | Image snapshots | ImageRenderer | Native SwiftUI replacement for ViewShot |


Implementation Phases

Phase 1: Foundation

Create Xcode project + OdakKit package with all domain logic and data layer.

Files to create:

  • OdakKit/Package.swift — local package with GRDB dependency
  • OdakKit/Sources/OdakKit/Models/ — all domain types:
    • TimerTypes.swiftTimerPhase, TimerState, TimerEvent, ActiveTimerState, HoldingState, TimerDisplayState, FocusSettings (port from domain/types.ts)
    • Preset.swiftPresetId, Preset, static quick/standard/deep definitions (port from domain/models/Preset.ts)
    • FocusSession.swiftFocusSession with computed dateKey/weekKey/monthKey (port from domain/models/Session.ts)
    • Achievement.swiftAchievementDefinition, AchievementProgress, AchievementCategory, CriteriaType, all 25 static definitions (port from domain/models/Achievement.ts)
    • Event.swiftAheadEvent, SinceEvent Codable structs
    • Stats.swiftStreakData, DailyStats, ComputedStats
  • OdakKit/Sources/OdakKit/Domain/TimerEngine.swift — pure reducer function, 1:1 port of domain/TimerEngine.ts
  • OdakKit/Sources/OdakKit/Domain/AchievementEngine.swift — processing pipeline with protocol-based criteria checkers (port from domain/achievements/AchievementEngine.ts)
  • OdakKit/Sources/OdakKit/Data/OdakDatabase.swift — GRDB wrapper with exact schema from data/database/OdakDatabase.ts (tables, views, indexes, generated columns)
  • OdakKit/Sources/OdakKit/Data/AppGroupStorage.swift — UserDefaults wrapper matching all MMKV keys from utils/storage.ts
  • OdakKit/Sources/OdakKit/Data/ImageStorage.swift — App Group container image save/load/delete
  • OdakKit/Sources/OdakKit/Theme/AccentColor.swift — 9 accent colors (blue, green, orange, yellow, pink, red, mint, purple, brown) mapping to system colors
  • OdakKit/Sources/OdakKit/Theme/OdakTheme.swift — spacing (4/8/16/24/32/48), borderRadius (8/16/24/32/9999), typography sizes, all color tokens from theme/unistyles.ts

Reference files to port from:

  • domain/TimerEngine.ts — state machine reducer (6 phases, 10 events)
  • domain/types.ts — all type definitions and constants
  • domain/models/Preset.ts — preset definitions
  • domain/models/Achievement.ts — 25 achievement definitions
  • domain/achievements/AchievementEngine.ts — achievement processing pipeline + SQL queries
  • data/database/OdakDatabase.ts — complete SQLite DDL, views, indexes
  • utils/storage.ts — all MMKV keys, types, defaults
  • theme/unistyles.ts — complete theme tokens

Phase 2: App Shell + Focus Timer (Core Experience)

Build the navigation skeleton and the most important screen — the focus timer.

Files to create:

  • OdakApp/App/OdakApp.swift — @main with WindowGroup, environment injection, theme initialization
  • OdakApp/App/AppState.swift — root @Observable holding database, storage, theme manager, child VMs
  • OdakApp/Navigation/AppTabView.swift — 4-tab TabView (Focus, Dates, Stats, You) with SF Symbols
  • OdakApp/Features/Focus/FocusScreen.swift — timer orchestrator with preset selector (idle), countdown (active), break overlay
  • OdakApp/Features/Focus/FocusViewModel.swift — @Observable wrapping TimerEngine, driving ticks via Timer, managing Live Activity
  • OdakApp/Features/Focus/FocusDotGrid.swift — Canvas + TimelineView rendering 10x5 dot grid with decay animation on current dot
  • OdakApp/Features/Focus/SwipeToFocusButton.swift — hold-to-start (1.5s idle / 2.0s focusing), breathing animation, DotRing integration
  • OdakApp/Features/Focus/DotRing.swift — 12 dots at 30deg intervals, Canvas-drawn, fills during hold, celebration spring at completion
  • OdakApp/Features/Focus/PresetSelector.swift — 3 circular buttons (10/25/50), accent bg when selected, glass otherwise
  • OdakApp/Features/Focus/BreakOverlay.swift — full accent-colored background, single-row dot grid, skip button
  • OdakApp/Components/AdaptiveCard.swift — iOS 26 glassEffect → .ultraThinMaterial → solid background fallback
  • OdakApp/Utils/HapticManager.swift — light/medium/heavy impact + notification success/warning wrappers
  • OdakApp/Utils/Capabilities.swifthasLiquidGlassSupport check

Reference files:

  • app/(tabs)/focus/index.tsx — focus screen layout, phase transitions, debug overlay
  • components/focus/DotGrid.tsx — dot rendering, decay animation, charging, pan gesture
  • components/focus/SwipeToFocus.tsx — hold gesture, breathing, DotRing, progress tracking
  • components/focus/DotRing.tsx — 12-dot ring layout, progress-driven fill
  • components/focus/SealButton.tsx — circular progress ring, break-the-seal
  • components/ui/AdaptiveCard.tsx — glass/blur/solid fallback pattern

Phase 3: Time Visualization Screen

Build the dot grid time visualization with all 7 view types and 8 dot shapes.

Files to create:

  • OdakApp/Features/Time/TimeScreen.swift — view type switching, header with context menu, share button
  • OdakApp/Features/Time/TimeDotGrid.swift — Canvas rendering all view types (now/today/month/year/life/since/ahead) with pan gesture
  • OdakApp/Features/Time/TimeHeader.swift — left pill (Menu for view type), right pill (time remaining / dot label), share pill
  • OdakApp/Features/Time/DotGridConfig.swift — per-view-type configuration: total dots, columns, gap sizes, dot sizing formula
  • OdakApp/Components/DotShapes.swift — 8 shape renderers for Canvas (circle, square, diamond, star, heart, hexagon, x, hash)
  • OdakApp/Components/AdaptivePillButton.swift — glass pill with text/icon content

Reference files:

  • app/(tabs)/index.tsx — all view type logic, dot sizing formula, column configs, pan gesture hit testing

Phase 4: Dates Screen (Events)

Build countdown/milestone events with image backgrounds and CRUD.

Files to create:

  • OdakApp/Features/Dates/DatesScreen.swift — segmented picker (Ahead/Since), grid/list toggle, sort options, toolbar
  • OdakApp/Features/Dates/DatesViewModel.swift — @Observable managing events from AppGroupStorage
  • OdakApp/Features/Dates/TimeCard.swift — ImageBackground with dark overlay, day count + title, grid (1:1) and list (170pt) modes
  • OdakApp/Features/Dates/AddEventSheet.swift — PHPicker for image, title input (35 char max), DatePicker graphical style
  • OdakApp/Features/Dates/EventDetailScreen.swift — parallax scroll header (350pt), real-time countdown, progress bar, calendar section

Reference files:

  • app/(tabs)/dates/index.tsx — dates screen with segmented picker, sort, grid/list
  • components/TimeCard.tsx — card layout and sizing
  • components/EventDetailScreen.tsx — parallax scroll implementation, countdown display

Phase 5: Stats Screen (Bank)

Build session analytics with charts and history.

Files to create:

  • OdakApp/Features/Stats/StatsScreen.swift — today stats strip, chart cards, session heatmap, staggered animations
  • OdakApp/Features/Stats/StatsViewModel.swift — @Observable loading session data from GRDB
  • OdakApp/Features/Stats/BarChartView.swift — Swift Charts BarMark for 7/30/90 day ranges, accent highlighting for current period
  • OdakApp/Features/Stats/SessionHeatmap.swift — row of 16pt colored circles for recent sessions
  • OdakApp/Features/Stats/SessionHistoryScreen.swift — SectionList grouped by day, session cards with completion badges

Reference files:

  • app/(tabs)/bank/index.tsx — stats layout, chart configurations, heatmap
  • app/(tabs)/bank/history.tsx — section list with deduplication

Phase 6: You Screen (Profile & Achievements)

Build profile with streaks, stats, and achievement system.

Files to create:

  • OdakApp/Features/You/YouScreen.swift — streak card, stats cards, daily goal dots, awards summary
  • OdakApp/Features/You/YouViewModel.swift — @Observable loading from GRDB + AppGroupStorage
  • OdakApp/Features/You/StreakCard.swift — accent bg, flame SF Symbol (tier-based), scale-on-tap animation
  • OdakApp/Features/You/DailyGoalDots.swift — filled/empty dot row
  • OdakApp/Features/You/AwardsGallery.swift — 3-col grid (5 on iPad), locked (gray 0.5 opacity) / unlocked (accent)
  • OdakApp/Features/You/AwardDetail.swift — always black bg, ZoomIn entrance animation, progress bar or earned date

Reference files:

  • app/(tabs)/you/index.tsx — profile layout, streak calculation display
  • app/(tabs)/you/awards.tsx — awards grid layout
  • app/(tabs)/you/award/[id].tsx — detail with dark background

Phase 7: Modals (Settings, Share, Welcome)

Files to create:

  • OdakApp/Features/Modals/SettingsSheet.swift — Form-style: accent color picker (9 options), theme mode (3), focus settings (toggles + pickers), detents [60%, 100%]
  • OdakApp/Features/Modals/ShareSheet.swift — card preview with theme/color/shape pickers, ImageRenderer for capture, tilt animation, UIActivityViewController
  • OdakApp/Features/Modals/WelcomeSheet.swift — 5 feature cards in 2-column LazyVGrid, CTA button with haptic

Reference files:

  • app/settings.tsx — all setting rows, accent color options, theme options
  • app/share.tsx — card customization, ViewShot capture, share action
  • app/welcome.tsx — 5 feature cards, layout

Phase 8: System Integration

Port existing Swift widget/Live Activity code to use shared OdakKit package.

Files to modify/create:

  • OdakWidget/ — refactor existing targets/widget/OdakWidget.swift to import OdakKit for models and storage instead of duplicating code
  • OdakLiveActivity/ — refactor existing ios/LiveActivity/*.swift files to import OdakKit, expose ActivityAttributes in shared package
  • OdakApp/Services/LiveActivityService.swift — port services/liveActivity/LiveActivityService.ts to Swift ActivityKit calls
  • OdakApp/Services/NotificationService.swift — UNUserNotificationCenter for session completion notifications

Reference files:

  • targets/widget/OdakWidget.swift — existing widget (22.6KB, already Swift)
  • ios/LiveActivity/*.swift — existing Live Activity (already Swift)
  • services/liveActivity/LiveActivityService.ts — activity lifecycle management
  • services/liveActivity/configs.ts — visual configuration for focus/break activities

Phase 9: Polish & Migration

  • iOS 26 Liquid Glass conditional modifiers on all cards/headers
  • iPad adaptive layouts (@Environment(\.horizontalSizeClass))
  • Accessibility: labels on all interactive elements, AccessibilityCustomAction for long-press gestures, reduced motion support
  • Data migration from RN version: read existing MMKV store + SQLite from app sandbox, copy to App Group container, run AchievementEngine.recomputeAll()

Verification

  1. Build: xcodebuild -scheme Odak -destination 'platform=iOS Simulator,name=iPhone 16 Pro'
  2. All tabs render: Launch app, verify 4 tabs load without crashes
  3. Focus timer flow: Hold to start → dots count down → break → skip break → idle
  4. Time screen: Switch between all 7 view types, verify dot counts, pan gesture with haptics
  5. Dates: Add event with image, verify grid/list views, verify countdown in detail
  6. Stats: Complete a session, verify it appears in charts and history
  7. Achievements: Complete sessions to trigger achievements, verify unlock in awards gallery
  8. Settings: Change accent color and theme, verify immediate update across all screens
  9. Share: Generate share card, verify ImageRenderer produces correct snapshot
  10. Widget: Install widget, verify it shows event data from App Group
  11. Live Activity: Start focus session, verify Dynamic Island countdown appears
  12. iOS 26 glass: Run on iOS 26 simulator, verify glass effects render; run on iOS 17 simulator, verify blur/solid fallbacks
← All notes