FortuneTeller

A mystical fortune-telling application for iOS built with SwiftUI. Features multiple prediction modes including Eight Ball, Daily Fortune, Fortune Cookie, and Tarot-inspired readings, all powered by an offline combinatorial generation engine.

Key Decisions

DecisionRationaleOutcome
MVVM architecture with @MainActor ViewModelsClean separation of UI and logic, thread-safe4 ViewModels drive 4 modes cleanly
PredictionProvider protocolAbstraction enables swappable prediction enginesAlgorithmicProvider implements current logic
Offline combinatorial word-pool predictionOffline-first, instant results, zero latencyThousands of unique combinations generated
17% humor injection chanceSurprise delight factor in fortunesUsers occasionally get humor results
ThemeManager as @EnvironmentObjectCentralized, reactive theming across all viewsTheme changes propagate instantly
UserDefaults for all persistenceSimple, no CoreData overhead for small data setsTheme, streaks, history all persisted
Lazy-loaded haptic generatorsNo startup overhead, generators created on first useSnappy performance
AudioServicesPlaySystemSound for SFXLightweight, no AVAudioPlayer complexity neededMinimal code, reliable system sounds
CAEmitterLayer via UIViewRepresentableHardware-accelerated particles for mystical effectsSmooth sparkle and mystical visuals
Calendar date methods for streak logicReliable day comparison vs simple DateComponentsCorrect isDateInToday/Yesterday detection
Init parameter DI for HistoryManagerViewModels can’t use @EnvironmentObject, need testable DITestable, optional parameter with fallback
100-item history limitPrevents unbounded UserDefaults growthAutomatic trimming on save
Locked themes visible at opacity(0.6)Clear visual indication of lock statusMotivates users to unlock new themes

Patterns Established

PatternDescriptionReusable
MVVM with @MainActor ViewModelsOne ViewModel per fortune modeYes
Protocol-based service abstractionPredictionProvider protocol with AlgorithmicProvider implYes
Word pool combinatorial generationSubjects + verbs + outcomes with varied sentence structuresYes
ThemeManager @EnvironmentObjectApp-wide theming via single observable managerYes
Singleton streak trackingDaily visit tracking with recordVisit() logicYes
History with deduplication and limitHistoryManager saves predictions, caps at 100 itemsYes
Achievement system unlockAchievementManager checks streaks and usage milestonesYes
ParticleEmitterView wrapperCAEmitterLayer wrapped for SwiftUI with 3 particle stylesYes
HapticService with named patternsSemantic haptic API independent of UIKit detailsYes

Lessons Learned

LessonContextImpact
System sounds are lightweight and reliableUsing AudioServicesPlaySystemSound instead of AVAudioPlayerReduced audio management overhead
Extract all colors to theme systemHardcoded hex colors in cookie view created maintenance burdenExtract to theme system for consistency
Avoid try? for silent JSON encodingFailing silently can lead to data loss without user notificationUse do/catch with logging at minimum
Timezone-aware daily fortune keysTimezone changes could cause duplicate daily fortunesUse Calendar.startOfDay for consistent date key generation
CAEmitterLayer outperforms CanvasHardware-accelerated particles vs SwiftUI Canvas renderingHandles 30-40 particles smoothly on device

Technical Debt

ItemPriorityNotes
Empty test suiteHighNo automated tests; AlgorithmicProvider is a priority target
Silent JSON encoding errorsMediumDailyFortuneViewModel persistence uses try? with no handling
Hardcoded cookie colorsLowColors in FortuneCookieView.swift should be in theme
Undocumented magic numbersLowhumorChance = 0.17 lacks documented rationale
Timezone fragility in keysMediumdailyFortune_yyyy-MM-dd could duplicate on shift
TestFlight distribution pendingMediumPhase 4 planned but not executed