Version casi lista
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
@MainActor
|
||||
final class OnboardingViewModel: ObservableObject {
|
||||
@Published var currentStep: Int = 0
|
||||
@Published var selectedMealWindows: MealWindows = .dinnerOnly
|
||||
@Published var includeWeekends: Bool = true
|
||||
@Published var syncCalendar: Bool = false
|
||||
@Published var selectedCalendarId: String?
|
||||
@Published var lunchTime: Date = {
|
||||
var c = DateComponents(); c.hour = 14; c.minute = 0
|
||||
return Calendar.current.date(from: c) ?? Date()
|
||||
}()
|
||||
@Published var dinnerTime: Date = {
|
||||
var c = DateComponents(); c.hour = 21; c.minute = 0
|
||||
return Calendar.current.date(from: c) ?? Date()
|
||||
}()
|
||||
|
||||
// First dishes
|
||||
@Published var newDishName: String = ""
|
||||
@Published var newDishTags: Set<UUID> = []
|
||||
@Published var addedDishes: [(name: String, tagIds: [UUID])] = []
|
||||
|
||||
let totalSteps = 5
|
||||
|
||||
var canContinue: Bool {
|
||||
switch currentStep {
|
||||
case 0: return true // Welcome
|
||||
case 1: return true // Meal windows (always has selection)
|
||||
case 2: return true // Weekends (toggle)
|
||||
case 3: return true // Calendar (optional)
|
||||
case 4: return addedDishes.count >= 2 // Need at least 2 dishes
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
var canAddDish: Bool {
|
||||
!newDishName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
}
|
||||
|
||||
func addDish(defaultTagId: UUID? = nil) {
|
||||
guard canAddDish else { return }
|
||||
let normalizedName = newDishName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !addedDishes.contains(where: { $0.name.lowercased() == normalizedName.lowercased() }) else { return }
|
||||
|
||||
let tagIds: [UUID]
|
||||
if !newDishTags.isEmpty {
|
||||
tagIds = Array(newDishTags)
|
||||
} else if let defaultTagId {
|
||||
tagIds = [defaultTagId]
|
||||
} else {
|
||||
tagIds = []
|
||||
}
|
||||
|
||||
addedDishes.append((name: normalizedName, tagIds: tagIds))
|
||||
newDishName = ""
|
||||
newDishTags = []
|
||||
}
|
||||
|
||||
func removeDish(at index: Int) {
|
||||
guard addedDishes.indices.contains(index) else { return }
|
||||
addedDishes.remove(at: index)
|
||||
}
|
||||
|
||||
func addSuggestedDish(name: String, preferredTagNames: [String], availableTags: [Tag]) {
|
||||
let normalizedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !normalizedName.isEmpty else { return }
|
||||
guard !addedDishes.contains(where: { $0.name.lowercased() == normalizedName.lowercased() }) else { return }
|
||||
|
||||
let matchingTags = availableTags.filter { tag in
|
||||
preferredTagNames.contains(where: { preferred in
|
||||
tag.name.caseInsensitiveCompare(preferred) == .orderedSame ||
|
||||
tag.nameEN.caseInsensitiveCompare(preferred) == .orderedSame
|
||||
})
|
||||
}
|
||||
|
||||
let tagIds: [UUID]
|
||||
if !matchingTags.isEmpty {
|
||||
tagIds = matchingTags.map(\.id)
|
||||
} else if let fallback = availableTags.sorted(by: { $0.sortOrder < $1.sortOrder }).first {
|
||||
tagIds = [fallback.id]
|
||||
} else {
|
||||
tagIds = []
|
||||
}
|
||||
|
||||
addedDishes.append((name: normalizedName, tagIds: tagIds))
|
||||
}
|
||||
|
||||
func nextStep() {
|
||||
if currentStep < totalSteps - 1 {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
currentStep += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func previousStep() {
|
||||
if currentStep > 0 {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
currentStep -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func completeOnboarding(context: ModelContext) -> Bool {
|
||||
// Create or get settings
|
||||
let descriptor = FetchDescriptor<AppSettings>()
|
||||
let settings = (try? context.fetch(descriptor))?.first ?? {
|
||||
let s = AppSettings()
|
||||
context.insert(s)
|
||||
return s
|
||||
}()
|
||||
|
||||
settings.mealWindowsEnum = selectedMealWindows
|
||||
settings.includeWeekends = includeWeekends
|
||||
settings.syncEnabled = syncCalendar
|
||||
settings.calendarId = selectedCalendarId
|
||||
settings.lunchTime = lunchTime
|
||||
settings.dinnerTime = dinnerTime
|
||||
settings.onboardingCompleted = true
|
||||
|
||||
// Create default tags if not exist
|
||||
let tagDescriptor = FetchDescriptor<Tag>()
|
||||
if (try? context.fetch(tagDescriptor))?.isEmpty ?? true {
|
||||
DefaultDataService.createDefaultTags(context: context)
|
||||
}
|
||||
|
||||
// Create dishes
|
||||
for dishData in addedDishes {
|
||||
let dish = Dish(name: dishData.name, tagIds: dishData.tagIds)
|
||||
context.insert(dish)
|
||||
}
|
||||
|
||||
// Create first week plan
|
||||
let weekStart = Date().startOfWeek()
|
||||
let _ = DefaultDataService.createWeekPlan(for: weekStart, settings: settings, context: context)
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
return true
|
||||
} catch {
|
||||
print("Onboarding save failed: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user