MixBoardApp.swift 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import SwiftData
  2. import SwiftUI
  3. /// MixBoard iOS — A mobile music player and mix preparation tool.
  4. /// Listen to your own MP3/FLAC files, build playlists, and sync them to the Mac app for DAW export.
  5. @main
  6. struct MixBoardApp: App {
  7. @State private var playerVM = PlayerViewModel()
  8. @State private var playlistVM = PlaylistViewModel()
  9. @StateObject private var libraryManager = LibraryManager()
  10. @StateObject private var theme = AppTheme()
  11. @StateObject private var syncManager = SyncManager()
  12. let modelContainer: ModelContainer
  13. init() {
  14. // One-time database reset to clear corrupted Track/p2 data + add cloud fields
  15. let needsReset = !UserDefaults.standard.bool(forKey: "dbResetV7")
  16. if needsReset {
  17. // Nuclear option: find and delete ALL .store and .sqlite files in the app container
  18. let fm = FileManager.default
  19. let home = fm.urls(for: .libraryDirectory, in: .userDomainMask).first!
  20. .deletingLastPathComponent() // gets the app container root
  21. if let enumerator = fm.enumerator(at: home, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) {
  22. for case let fileURL as URL in enumerator {
  23. let name = fileURL.lastPathComponent
  24. if name == "default.store" || name == "default.store-shm" || name == "default.store-wal"
  25. || name.hasSuffix(".sqlite") || name.hasSuffix(".sqlite-shm") || name.hasSuffix(".sqlite-wal") {
  26. try? fm.removeItem(at: fileURL)
  27. }
  28. }
  29. }
  30. UserDefaults.standard.set(true, forKey: "dbResetV7")
  31. }
  32. do {
  33. modelContainer = try ModelContainer(for: Track.self, CuePoint.self, Playlist.self, PlaylistEntry.self, PlaylistFolder.self)
  34. } catch {
  35. fatalError("Failed to create ModelContainer: \(error)")
  36. }
  37. // Clean up leftover mock state for non-mock UI tests
  38. if ProcessInfo.processInfo.arguments.contains("-UITesting") && !ProcessInfo.processInfo.arguments.contains("-MockNetwork") {
  39. UserDefaults.standard.removeObject(forKey: "chadMusic.serverURL")
  40. KeychainService.deleteAPIKey()
  41. }
  42. // Register mock URL protocol for CI/UI testing with stubbed network
  43. if ProcessInfo.processInfo.arguments.contains("-MockNetwork") {
  44. MockURLProtocol.registerMockResponses()
  45. // Set dummy server config so the API client considers itself configured
  46. UserDefaults.standard.set("http://localhost:9999", forKey: "chadMusic.serverURL")
  47. try? KeychainService.saveAPIKey("mock-test-key")
  48. }
  49. }
  50. var body: some Scene {
  51. WindowGroup {
  52. ContentView()
  53. .environment(playerVM)
  54. .environment(playlistVM)
  55. .environmentObject(libraryManager)
  56. .environmentObject(theme)
  57. .environmentObject(syncManager)
  58. .preferredColorScheme(theme.preferredColorScheme)
  59. .onAppear {
  60. MediaKeyHandler.shared.register(playerVM: playerVM)
  61. }
  62. }
  63. .modelContainer(modelContainer)
  64. }
  65. }