platos: paywall inmediato al llegar al limite y orden alfabetico
- attemptAddDish/attemptCreateDish: el limite gratuito se comprueba en el tap de anadir plato (DishListView y los 3 puntos de Home) y muestra el DishLimitModal -> paywall directamente; antes solo se comprobaba al guardar el formulario y el usuario podia rellenarlo entero para nada - DishLimitModal compartido (era private de DishFormView) y PaywallPresentation promovido a PremiumView.swift; DishListView usa sheet(item:) tambien para el banner (source dish_counter vs dish_limit) - Boton en la toolbar de Mis Platos que alterna orden alfabetico (persistido en dish_list_alphabetical_sort); borrado por swipe corregido para usar la lista mostrada Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
This commit is contained in:
@@ -16,6 +16,7 @@ struct HomeView: View {
|
||||
// separate @State vars the sheet sometimes rendered before the source was
|
||||
// set and logged paywall_viewed as "unknown".
|
||||
@State private var paywallPresentation: PaywallPresentation?
|
||||
@State private var showDishLimitModal: Bool = false
|
||||
@State private var showMonthlyHistory: Bool = false
|
||||
@State private var showStats: Bool = false
|
||||
@State private var showWeekLimitUpsell: Bool = false
|
||||
@@ -451,6 +452,19 @@ struct HomeView: View {
|
||||
.sheet(isPresented: $viewModel.showDishForm) {
|
||||
DishFormView()
|
||||
}
|
||||
.sheet(isPresented: $showDishLimitModal) {
|
||||
DishLimitModal(
|
||||
onSeePremium: {
|
||||
showDishLimitModal = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
|
||||
paywallPresentation = PaywallPresentation(source: "dish_limit")
|
||||
}
|
||||
},
|
||||
onDismiss: { showDishLimitModal = false }
|
||||
)
|
||||
.presentationDetents([.medium])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.sheet(isPresented: $showWeekPicker) {
|
||||
NavigationStack {
|
||||
Form {
|
||||
@@ -516,7 +530,7 @@ struct HomeView: View {
|
||||
onCreateDish: {
|
||||
selectedEmptySlotId = nil
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
viewModel.showDishForm = true
|
||||
attemptCreateDish()
|
||||
}
|
||||
},
|
||||
onEatingOut: {
|
||||
@@ -554,7 +568,7 @@ struct HomeView: View {
|
||||
onCreateDish: {
|
||||
selectedFilledSlotId = nil
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
viewModel.showDishForm = true
|
||||
attemptCreateDish()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -653,11 +667,6 @@ struct HomeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct PaywallPresentation: Identifiable {
|
||||
let id = UUID()
|
||||
let source: String
|
||||
}
|
||||
|
||||
private struct SlotDishPickerSheet: View {
|
||||
let dishes: [Dish]
|
||||
let tags: [Tag]
|
||||
@@ -804,7 +813,7 @@ struct HomeView: View {
|
||||
language: settings.languageEnum.resolved(),
|
||||
usedDishIds: Set(plan.slotList.compactMap(\.dishId)),
|
||||
usageRanking: dishUsageCounts,
|
||||
onAddDish: { viewModel.showDishForm = true },
|
||||
onAddDish: { attemptCreateDish() },
|
||||
onQuickAssignDish: { dish in
|
||||
viewModel.assignDishToFirstFreeSlot(
|
||||
dish,
|
||||
@@ -1183,6 +1192,20 @@ struct HomeView: View {
|
||||
AnalyticsService.logEvent("widget_promo_dismissed")
|
||||
}
|
||||
|
||||
/// Gate every "create dish" entry point on the free limit — the check used
|
||||
/// to live only in the form's save button, so a user at the limit could
|
||||
/// open and fill the whole form before hitting the paywall.
|
||||
private func attemptCreateDish() {
|
||||
let isPremium = allSettings.first?.isPremium ?? false
|
||||
if PremiumAccess.hasReachedFreeDishLimit(dishCount: dishes.count, isPremium: isPremium) {
|
||||
AnalyticsService.logDishLimitHit()
|
||||
HapticManager.shared.notification(type: .warning)
|
||||
showDishLimitModal = true
|
||||
} else {
|
||||
viewModel.showDishForm = true
|
||||
}
|
||||
}
|
||||
|
||||
private func evaluateReviewPrompt() {
|
||||
let descriptor = FetchDescriptor<WeekPlan>()
|
||||
guard let plans = try? context.fetch(descriptor) else { return }
|
||||
|
||||
Reference in New Issue
Block a user