ce88d7b265
- ShoppingItem model (per-week lines; dish-derived or free-text) + schema. - ShoppingListService.reconcile mirrors planned dishes' ingredients into the list, preserving check-off state and user items; plain-text export helper. - ShoppingListView: grouped by dish, check-off, swipe-delete, free-text adds, clear-checked, ShareLink export, and inline on-device "Generate" for planned dishes without ingredients (paywall after 3 free generations, source=ingredient_generation). - Home toolbar: cart entry point (all users) + sheet. - Analytics: shopping_list_opened, shopping_item_added, ingredients_generated. - Localized in all 6 languages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BkanrydYtrme8wipTzWssG
25 lines
890 B
Swift
25 lines
890 B
Swift
import Foundation
|
|
|
|
enum PremiumAccess {
|
|
static let freeDishLimit = 15
|
|
static let freeFutureWeeks = 1
|
|
|
|
/// Free users get a taste of on-device ingredient generation; premium is
|
|
/// unlimited. Counted per install via UserDefaults.
|
|
static let freeIngredientGenerations = 3
|
|
private static let generationCountKey = "ingredient_generation_count"
|
|
|
|
static func hasReachedFreeDishLimit(dishCount: Int, isPremium: Bool) -> Bool {
|
|
!isPremium && dishCount >= freeDishLimit
|
|
}
|
|
|
|
static func canGenerateIngredients(isPremium: Bool) -> Bool {
|
|
isPremium || UserDefaults.standard.integer(forKey: generationCountKey) < freeIngredientGenerations
|
|
}
|
|
|
|
static func recordIngredientGeneration() {
|
|
let count = UserDefaults.standard.integer(forKey: generationCountKey)
|
|
UserDefaults.standard.set(count + 1, forKey: generationCountKey)
|
|
}
|
|
}
|