1.0.4: Smarter auto-assign, quick dish entry, calendar fix, frictionless onboarding
- AutocompleteEngine: no-repeat rule now applies to all dishes (not just tagless ones); fallback still allows repeats when no alternative exists - CalendarService: update existing events instead of delete+create to prevent duplicates; fix weekStartDate using plan date instead of currentWeekStart - DishFormView: form stays open after saving in create mode for quick multi-dish entry; Done button to close; saved feedback banner - OnboardingViewModel/FirstDishesStepView: dishes are now optional during onboarding; Finish button always enabled - AnalyticsService: new events — onboarding_step_viewed(step) and auto_assign_used(filled, unfilled) - Localization: dish_done and first_dishes_optional_hint added in 6 languages; release notes updated in all 6 languages - Version bump: 1.0.3 → 1.0.4 (build 19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ struct DishFormView: View {
|
||||
@StateObject private var viewModel = DishViewModel()
|
||||
@State private var showPremium = false
|
||||
@State private var showDishLimitUpsell = false
|
||||
@State private var showSavedFeedback = false
|
||||
|
||||
var dish: Dish?
|
||||
|
||||
@@ -30,6 +31,20 @@ struct DishFormView: View {
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
if showSavedFeedback {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
Text("toast_dish_saved")
|
||||
}
|
||||
.font(.mealMoodSmall)
|
||||
.foregroundColor(.mealMoodSuccess)
|
||||
.padding(.vertical, 10)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.mealMoodSuccess.opacity(0.1))
|
||||
.cornerRadius(10)
|
||||
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||
}
|
||||
|
||||
// Name field
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("dish_name_label")
|
||||
@@ -150,7 +165,7 @@ struct DishFormView: View {
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("dish_cancel") { dismiss() }
|
||||
Button(viewModel.isEditing ? "dish_cancel" : "dish_done") { dismiss() }
|
||||
.foregroundColor(.mealMoodTextSecondary)
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
@@ -163,8 +178,17 @@ struct DishFormView: View {
|
||||
HapticManager.shared.notification(type: .warning)
|
||||
return
|
||||
}
|
||||
let wasEditing = viewModel.isEditing
|
||||
viewModel.save(context: context)
|
||||
dismiss()
|
||||
if wasEditing {
|
||||
dismiss()
|
||||
} else {
|
||||
withAnimation { showSavedFeedback = true }
|
||||
HapticManager.shared.notification(type: .success)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
|
||||
withAnimation { showSavedFeedback = false }
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "checkmark")
|
||||
.fontWeight(.semibold)
|
||||
|
||||
@@ -51,6 +51,13 @@ struct FirstDishesStepView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
Text("first_dishes_optional_hint")
|
||||
.font(.mealMoodCaption)
|
||||
.foregroundColor(.mealMoodCoral.opacity(0.8))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, -16)
|
||||
|
||||
// New dish form
|
||||
VStack(spacing: 12) {
|
||||
TextField("first_dishes_name_placeholder", text: $viewModel.newDishName)
|
||||
@@ -232,7 +239,7 @@ struct FirstDishesStepView: View {
|
||||
HapticManager.shared.notification(type: .success)
|
||||
onFinish()
|
||||
},
|
||||
isEnabled: viewModel.addedDishes.count >= 2
|
||||
isEnabled: true
|
||||
)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 12)
|
||||
|
||||
@@ -79,6 +79,10 @@ struct OnboardingView: View {
|
||||
.onAppear {
|
||||
ensureDefaultTagsIfNeeded()
|
||||
Task { await restoreFromICloudAndFinishIfNeeded() }
|
||||
AnalyticsService.logOnboardingStepViewed(step: 0)
|
||||
}
|
||||
.onChange(of: viewModel.currentStep) { _, newStep in
|
||||
AnalyticsService.logOnboardingStepViewed(step: newStep)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user