// // DataStore.swift // Werkout_ios // // Created by Trey Tartt on 6/19/23. // import Foundation import SwiftUI import SharedCore class DataStore: ObservableObject { enum DataStoreStatus { case loading case idle } static let shared = DataStore() private let runtimeReporter = RuntimeReporter.shared public private(set) var allWorkouts: [Workout]? public private(set) var allMuscles: [Muscle]? public private(set) var allEquipment: [Equipment]? public private(set) var allExercise: [Exercise]? public private(set) var allNSFWVideos: [NSFWVideo]? @Published public private(set) var status = DataStoreStatus.idle private var pendingFetchCompletions = [() -> Void]() public func randomVideoFor(gender: String) -> String? { return allNSFWVideos?.filter({ $0.genderValue.lowercased() == gender.lowercased() }).randomElement()?.videoFile ?? nil } public var nsfwGenderOptions: [String]? { let values = self.allNSFWVideos?.map({ $0.genderValue }) if let values = values { return Array(Set(values)) } return nil } public var workoutsUniqueUsers: [RegisteredUser]? { guard let workouts = allWorkouts else { return nil } let users = workouts.compactMap({ $0.registeredUser }) return Array(Set(users)) } public func fetchAllData(completion: @escaping (() -> Void)) { if status == .loading { pendingFetchCompletions.append(completion) runtimeReporter.recordInfo("fetchAllData called while already loading") return } pendingFetchCompletions = [completion] status = .loading let fetchAllDataQueue = DispatchGroup() fetchAllDataQueue.enter() fetchAllDataQueue.enter() fetchAllDataQueue.enter() fetchAllDataQueue.enter() fetchAllDataQueue.enter() fetchAllDataQueue.notify(queue: .main) { self.status = .idle let completions = self.pendingFetchCompletions self.pendingFetchCompletions.removeAll() completions.forEach { $0() } } AllWorkoutFetchable().fetch(completion: { result in switch result { case .success(let model): self.allWorkouts = model case .failure(let error): self.runtimeReporter.recordError("Failed to fetch workouts", metadata: ["error": error.localizedDescription]) } fetchAllDataQueue.leave() }) AllMusclesFetchable().fetch(completion: { result in switch result { case .success(let model): self.allMuscles = model.sorted(by: { $0.name < $1.name }) case .failure(let error): self.runtimeReporter.recordError("Failed to fetch muscles", metadata: ["error": error.localizedDescription]) } fetchAllDataQueue.leave() }) AllEquipmentFetchable().fetch(completion: { result in switch result { case .success(let model): self.allEquipment = model.sorted(by: { $0.name < $1.name }) case .failure(let error): self.runtimeReporter.recordError("Failed to fetch equipment", metadata: ["error": error.localizedDescription]) } fetchAllDataQueue.leave() }) AllExerciseFetchable().fetch(completion: { result in switch result { case .success(let model): self.allExercise = model.sorted(by: { $0.name < $1.name }) case .failure(let error): self.runtimeReporter.recordError("Failed to fetch exercises", metadata: ["error": error.localizedDescription]) } fetchAllDataQueue.leave() }) AllNSFWVideosFetchable().fetch(completion: { result in switch result { case .success(let model): self.allNSFWVideos = model case .failure(let error): self.runtimeReporter.recordError("Failed to fetch NSFW videos", metadata: ["error": error.localizedDescription]) } fetchAllDataQueue.leave() }) } func setupFakeData() { allWorkouts = PreviewData.allWorkouts() allMuscles = PreviewData.parseMuscle() allEquipment = PreviewData.parseEquipment() allExercise = PreviewData.parseExercises() } }