diff --git a/MealMood/Services/AnalyticsService.swift b/MealMood/Services/AnalyticsService.swift index 7aba1f5..8483ec2 100644 --- a/MealMood/Services/AnalyticsService.swift +++ b/MealMood/Services/AnalyticsService.swift @@ -21,6 +21,7 @@ enum AnalyticsEvent { // Planner static let autoAssignUsed = "auto_assign_used" + static let weekCopiedPrevious = "week_copied_previous" // Onboarding static let onboardingStepViewed = "onboarding_step_viewed" @@ -112,6 +113,13 @@ enum AnalyticsService { ]) } + static func logWeekCopiedPrevious(copiedSlots: Int, source: String) { + logEvent(AnalyticsEvent.weekCopiedPrevious, parameters: [ + "copied_slots": copiedSlots, + "source": source + ]) + } + static func logICloudSyncToggled(enabled: Bool) { logEvent(AnalyticsEvent.iCloudSyncToggled, parameters: ["enabled": enabled]) } diff --git a/MealMood/ViewModels/HomeViewModel.swift b/MealMood/ViewModels/HomeViewModel.swift index 985429d..513ec4b 100644 --- a/MealMood/ViewModels/HomeViewModel.swift +++ b/MealMood/ViewModels/HomeViewModel.swift @@ -266,7 +266,8 @@ final class HomeViewModel: ObservableObject { previousPlan: WeekPlan?, settings: AppSettings, allTags: [Tag], - allDishes: [Dish] + allDishes: [Dish], + source: String ) { guard let previousPlan else { showToastMessage(localizedString("toast_previous_week_empty", language: settings.languageEnum.resolved())) @@ -282,6 +283,7 @@ final class HomeViewModel: ObservableObject { } let dishIds = Set(allDishes.map(\.id)) + var copiedCount = 0 for slot in currentPlan.slots { if let eventId = slot.calendarEventId { CalendarService.shared.deleteEvent(eventId: eventId) @@ -291,6 +293,7 @@ final class HomeViewModel: ObservableObject { let key = SlotKey(dayOfWeek: slot.dayOfWeek, mealType: slot.mealType) if let previousDishId = previousByKey[key]?.dishId, dishIds.contains(previousDishId) { slot.dishId = previousDishId + copiedCount += 1 } else { slot.dishId = nil } @@ -301,6 +304,7 @@ final class HomeViewModel: ObservableObject { currentPlan.updatedAt = Date() applyCalendarSyncPolicy(plan: currentPlan, settings: settings) HapticManager.shared.notification(type: .success) + AnalyticsService.logWeekCopiedPrevious(copiedSlots: copiedCount, source: source) showToastMessage(localizedString("toast_copied_previous_week", language: settings.languageEnum.resolved())) } diff --git a/MealMood/Views/Home/HomeView.swift b/MealMood/Views/Home/HomeView.swift index 6c24314..f4577bd 100644 --- a/MealMood/Views/Home/HomeView.swift +++ b/MealMood/Views/Home/HomeView.swift @@ -23,6 +23,7 @@ struct HomeView: View { @State private var showWeekPicker: Bool = false @State private var weekPickerDate: Date = Date() @State private var showCopyPreviousConfirm: Bool = false + @State private var copyPreviousSource: String = "menu" @State private var showReviewSentimentPrompt: Bool = false @State private var showReviewSupportPrompt: Bool = false @State private var showPostOnboardingAutoAssignPrompt: Bool = false @@ -149,11 +150,7 @@ struct HomeView: View { .disabled(!viewModel.canUndo(for: plan)) Button { - if plan.slots.contains(where: { $0.dishId != nil }) { - showCopyPreviousConfirm = true - } else { - copyFromPreviousWeek(currentPlan: plan, settings: settings) - } + requestCopyPreviousWeek(plan: plan, settings: settings, source: "menu") } label: { Label("home_copy_previous_week", systemImage: "doc.on.doc") } @@ -774,32 +771,53 @@ struct HomeView: View { @ViewBuilder private func autoAssignBanner(emptyCount: Int, plan: WeekPlan, settings: AppSettings) -> some View { - HStack(spacing: 12) { - VStack(alignment: .leading, spacing: 2) { - Text("home_auto_assign_cta_title") - .font(.mealMoodBodyBold) - .foregroundColor(.mealMoodTextPrimary) - Text(String(format: String(localized: "home_auto_assign_cta_slots"), emptyCount)) - .font(.mealMoodCaption) - .foregroundColor(.mealMoodTextSecondary) - } - Spacer() - Button { - viewModel.autoComplete(plan: plan, dishes: dishes, tags: tags, settings: settings, allPlans: weekPlans) - } label: { - HStack(spacing: 6) { - Image(systemName: "wand.and.stars") - Text("home_auto_assign_cta_button") - .font(.mealMoodSmall.weight(.semibold)) + VStack(spacing: 10) { + HStack(spacing: 12) { + VStack(alignment: .leading, spacing: 2) { + Text("home_auto_assign_cta_title") + .font(.mealMoodBodyBold) + .foregroundColor(.mealMoodTextPrimary) + Text(String(format: String(localized: "home_auto_assign_cta_slots"), emptyCount)) + .font(.mealMoodCaption) + .foregroundColor(.mealMoodTextSecondary) } - .foregroundColor(.white) - .padding(.horizontal, 14) - .padding(.vertical, 8) - .background(Color.mealMoodCoral) - .clipShape(Capsule()) + Spacer() + Button { + viewModel.autoComplete(plan: plan, dishes: dishes, tags: tags, settings: settings, allPlans: weekPlans) + } label: { + HStack(spacing: 6) { + Image(systemName: "wand.and.stars") + Text("home_auto_assign_cta_button") + .font(.mealMoodSmall.weight(.semibold)) + } + .foregroundColor(.white) + .padding(.horizontal, 14) + .padding(.vertical, 8) + .background(Color.mealMoodCoral) + .clipShape(Capsule()) + } + .buttonStyle(.plain) + .disabled(viewModel.isAutoCompleting) + } + + if previousWeekHasMenu() { + Button { + requestCopyPreviousWeek(plan: plan, settings: settings, source: "empty_banner") + } label: { + HStack(spacing: 6) { + Image(systemName: "doc.on.doc") + Text("home_copy_previous_week") + .font(.mealMoodSmall.weight(.semibold)) + } + .foregroundColor(.mealMoodCoral) + .frame(maxWidth: .infinity) + .padding(.vertical, 8) + .background(Capsule().fill(Color.white.opacity(0.75))) + .overlay(Capsule().stroke(Color.mealMoodCoral.opacity(0.4), lineWidth: 1)) + } + .buttonStyle(.plain) + .disabled(viewModel.isAutoCompleting) } - .buttonStyle(.plain) - .disabled(viewModel.isAutoCompleting) } .padding(12) .background( @@ -869,6 +887,22 @@ struct HomeView: View { .padding(.horizontal, 16) } + private func previousWeekHasMenu() -> Bool { + guard let previous = fetchWeekPlan(for: viewModel.currentWeekStart.addingDays(-7)) else { return false } + return previous.slots.contains { $0.dishId != nil } + } + + /// Copies the previous week, asking to confirm first only when the current + /// week already has dishes that would be overwritten. + private func requestCopyPreviousWeek(plan: WeekPlan, settings: AppSettings, source: String) { + copyPreviousSource = source + if plan.slots.contains(where: { $0.dishId != nil }) { + showCopyPreviousConfirm = true + } else { + copyFromPreviousWeek(currentPlan: plan, settings: settings) + } + } + private func copyFromPreviousWeek(currentPlan: WeekPlan, settings: AppSettings) { let previousPlan = fetchWeekPlan(for: viewModel.currentWeekStart.addingDays(-7)) viewModel.copyFromPreviousWeek( @@ -876,7 +910,8 @@ struct HomeView: View { previousPlan: previousPlan, settings: settings, allTags: tags, - allDishes: dishes + allDishes: dishes, + source: copyPreviousSource ) }