diff --git a/CLAUDE.md b/CLAUDE.md index 3ada835..8c38e5f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ - **Name**: MealMood: Family Meal Planner - **Bundle ID**: com.alexandrevazquez.mealmood - **Platform**: iOS/iPadOS (also runs as iOS app on Mac) -- **Current version**: 1.0.4 +- **Current version**: 1.1.0 - **Main branch**: `1.0.1` (production), feature branches named after versions ## Credentials — all managed via `pass` (GPG-encrypted, syncs to Gitea) diff --git a/MealMood.xcodeproj/project.pbxproj b/MealMood.xcodeproj/project.pbxproj index 4f75ac7..3123a01 100644 --- a/MealMood.xcodeproj/project.pbxproj +++ b/MealMood.xcodeproj/project.pbxproj @@ -860,7 +860,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MealMood/Resources/MealMood.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 31; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = MealMood/Resources/Info.plist; @@ -869,7 +869,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.5; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.mealmood; PRODUCT_NAME = MealMood; SDKROOT = iphoneos; @@ -965,7 +965,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MealMood/Resources/MealMood.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 31; + CURRENT_PROJECT_VERSION = 46; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = MealMood/Resources/Info.plist; @@ -974,7 +974,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.5; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.mealmood; PRODUCT_NAME = MealMood; SDKROOT = iphoneos; diff --git a/MealMood/Configuration/MealMood.storekit b/MealMood/Configuration/MealMood.storekit index db1b232..59427ae 100644 --- a/MealMood/Configuration/MealMood.storekit +++ b/MealMood/Configuration/MealMood.storekit @@ -14,7 +14,21 @@ ], "products" : [ - + { + "displayPrice" : "2.99", + "familyShareable" : false, + "internalID" : "B2F4D1A0-3E7C-4F8B-9D2E-1A5C6B0E3F7D", + "localizations" : [ + { + "description" : "Lifetime access to all premium features.", + "displayName" : "MealMood Premium (Legacy)", + "locale" : "en_US" + } + ], + "productID" : "com.mealmood.premium.monthly", + "referenceName" : "MealMood Premium Legacy", + "type" : "NonConsumable" + } ], "settings" : { "_applicationInternalID" : "2147483647", @@ -60,7 +74,7 @@ "locale" : "en_US" } ], - "productID" : "com.mealmood.premium.monthly", + "productID" : "com.mealmood.premium.monthly.sub", "recurringSubscriptionPeriod" : "P1M", "referenceName" : "MealMood Premium Monthly", "subscriptionPricePointID" : "0" diff --git a/MealMood/Resources/Info.plist b/MealMood/Resources/Info.plist index 4c7b410..0aa15f9 100644 --- a/MealMood/Resources/Info.plist +++ b/MealMood/Resources/Info.plist @@ -15,11 +15,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.6 + 1.1.1 CFBundleVersion - 41 + 46 GADApplicationIdentifier ca-app-pub-1549720748100858~9985112590 + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS NSCalendarsFullAccessUsageDescription diff --git a/MealMood/Services/StoreManager.swift b/MealMood/Services/StoreManager.swift index 44b5c8f..6ffb1a6 100644 --- a/MealMood/Services/StoreManager.swift +++ b/MealMood/Services/StoreManager.swift @@ -27,11 +27,13 @@ final class StoreManager: ObservableObject { @Published var debugLoadedProductIds: [String] = [] @Published var debugLoadedProducts: [String] = [] - static let monthlyProductId = "com.mealmood.premium.monthly" + static let monthlyProductId = "com.mealmood.premium.monthly.sub" + static let legacyOneTimeProductId = "com.mealmood.premium.monthly" private var productIds: [String] { - var ids = [Self.monthlyProductId] + var ids = [Self.monthlyProductId, Self.legacyOneTimeProductId] if let bundleId = Bundle.main.bundleIdentifier { + ids.append("\(bundleId).premium.monthly.sub") ids.append("\(bundleId).premium.monthly") } @@ -115,42 +117,35 @@ final class StoreManager: ObservableObject { } var monthlyProduct: Product? { - if let match = products.first(where: { product in - product.id == Self.monthlyProductId && product.type == .autoRenewable - }) { + if let match = products.first(where: { $0.id == Self.monthlyProductId && $0.type == .autoRenewable }) { return match } - - if let match = products.first(where: { $0.id == Self.monthlyProductId }) { - return match - } - - if let match = products.first(where: { $0.id.contains("monthly") && $0.type == .autoRenewable }) { - return match - } - if let match = products.first(where: { $0.type == .autoRenewable }) { return match } - + if let match = products.first(where: { $0.id == Self.legacyOneTimeProductId }) { + return match + } return products.first } var debugProductIds: [String] { productIds } static func hasActiveSubscription() async -> Bool { - // Check all known product ID variants to handle both the canonical ID - // and any bundle-prefixed IDs that may have been used at purchase time. - var knownIds: [String] = [monthlyProductId] - if let bundleId = Bundle.main.bundleIdentifier { - knownIds.append("\(bundleId).premium.monthly") - } - let ids = Set(knownIds) + let bundleId = Bundle.main.bundleIdentifier ?? "" + let subscriptionIds: Set = [monthlyProductId, "\(bundleId).premium.monthly.sub"] + // Legacy one-time purchases grant lifetime access — no expiration check needed. + let legacyIds: Set = [legacyOneTimeProductId, "\(bundleId).premium.monthly"] for await result in Transaction.currentEntitlements { - if case .verified(let transaction) = result, - ids.contains(transaction.productID), - transaction.revocationDate == nil { + guard case .verified(let transaction) = result, + transaction.revocationDate == nil else { continue } + + if legacyIds.contains(transaction.productID) { + return true + } + + if subscriptionIds.contains(transaction.productID) { if let expirationDate = transaction.expirationDate, expirationDate < Date() { continue } diff --git a/MealMoodWidget/Info.plist b/MealMoodWidget/Info.plist index f43ee24..8256103 100644 --- a/MealMoodWidget/Info.plist +++ b/MealMoodWidget/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.0.6 + 1.1.1 CFBundleVersion - 41 + 46 NSExtension NSExtensionPointIdentifier diff --git a/fastlane/Deliverfile b/fastlane/Deliverfile index 1a8c05a..eceeead 100644 --- a/fastlane/Deliverfile +++ b/fastlane/Deliverfile @@ -1 +1,2 @@ run_precheck_before_submit(false) +force(true) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8ffc8c0..17cee9a 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -22,7 +22,7 @@ platform :ios do build_app( scheme: "MealMood", export_method: "app-store", - sdk: "iphoneos26.4", + sdk: "iphoneos26.5", export_options: EXPORT_OPTIONS ) upload_to_testflight(skip_waiting_for_build_processing: true) @@ -33,7 +33,7 @@ platform :ios do increment_build_number(xcodeproj: "MealMood.xcodeproj") build_app( scheme: "MealMood", - sdk: "iphoneos26.4", + sdk: "iphoneos26.5", export_options: EXPORT_OPTIONS ) upload_to_app_store(force: true) diff --git a/fastlane/metadata/de-DE/promotional_text.txt b/fastlane/metadata/de-DE/promotional_text.txt index 9911558..71106ff 100644 --- a/fastlane/metadata/de-DE/promotional_text.txt +++ b/fastlane/metadata/de-DE/promotional_text.txt @@ -1 +1 @@ -NEU in 1.0.6: Prioritätsgerichte, tagesbasierte Regeln, Gerichte direkt ersetzen und unvollständige Woche exportieren. +NEU in 1.1.0: Premium ist jetzt ein Monatsabo. Bestehende Einmalkäufer behalten ihren vollen Zugang automatisch. diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt index dc91469..8e9bbe2 100644 --- a/fastlane/metadata/de-DE/release_notes.txt +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -1,8 +1,4 @@ -Neu in Version 1.0.6: +Neu in Version 1.1.0: -• Prioritätsgerichte — markiere ein Gericht mit einem Stern, damit die Auto-Vervollständigung es zuerst wählt. -• Tagesregeln — Tag-Einschränkungen unterstützen jetzt "nur Wochentage" oder "nur Wochenende". -• Auto-ausfüllen-Schaltfläche — jetzt direkt sichtbar, wenn leere Slots vorhanden sind. -• Tippen zum Ersetzen — tippe auf einen gefüllten Slot, um das Gericht direkt zu tauschen. -• Jederzeit exportieren — teile deine Woche auch wenn sie unvollständig ist; leere Slots zeigen "Noch offen". -• Sonntags-Erinnerung — neue Benachrichtigung zum Planen der Woche vor Montag. +• Premium ist jetzt ein monatliches Abo — unbegrenzte Gerichte, Planung und Export für 2,99 €/Monat. +• Bestehende Einmalkäufer behalten ihren vollen Zugang automatisch — keine Aktion erforderlich. diff --git a/fastlane/metadata/en-US/promotional_text.txt b/fastlane/metadata/en-US/promotional_text.txt index 727f1ca..93b630f 100644 --- a/fastlane/metadata/en-US/promotional_text.txt +++ b/fastlane/metadata/en-US/promotional_text.txt @@ -1 +1 @@ -NEW in 1.0.6: Priority dishes, day-based tag rules, tap to replace meals, and export your week even when it's not complete. +NEW in 1.1.0: Premium is now a monthly subscription. Existing lifetime purchasers keep full access automatically. diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index 1915f93..b8c03a9 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,8 +1,4 @@ -New in 1.0.6: +New in 1.1.0: -• Priority dishes — star a dish to make auto-assign pick it first. -• Day rules — tag restrictions now support "weekdays only" or "weekend only". -• Auto-assign button — now prominently shown whenever there are empty slots. -• Tap to replace — tap any filled slot to swap the dish directly. -• Export anytime — share your week even if it's not complete yet; empty slots show "TBD". -• Sunday reminder — new notification to plan next week before Monday. +• Premium is now a monthly subscription — unlimited dishes, planning, and export for $2.99/month. +• Existing lifetime purchasers keep full access automatically, no action needed. diff --git a/fastlane/metadata/es-ES/promotional_text.txt b/fastlane/metadata/es-ES/promotional_text.txt index 1af5ef6..3587945 100644 --- a/fastlane/metadata/es-ES/promotional_text.txt +++ b/fastlane/metadata/es-ES/promotional_text.txt @@ -1 +1 @@ -NUEVO en 1.0.6: Platos prioritarios, reglas por días, toca para reemplazar y exporta tu semana aunque no esté completa. +NUEVO en 1.1.0: Premium ahora es una suscripción mensual. Los compradores previos conservan su acceso completo automáticamente. diff --git a/fastlane/metadata/es-ES/release_notes.txt b/fastlane/metadata/es-ES/release_notes.txt index 5a2064b..ee2e2cb 100644 --- a/fastlane/metadata/es-ES/release_notes.txt +++ b/fastlane/metadata/es-ES/release_notes.txt @@ -1,8 +1,4 @@ -Novedades en 1.0.6: +Novedades en 1.1.0: -• Platos prioritarios — marca un plato con estrella para que el auto-completar lo elija primero. -• Reglas de días — las restricciones de etiquetas ahora admiten "solo entre semana" o "solo fin de semana". -• Botón auto-rellenar — ahora visible directamente cuando hay huecos vacíos. -• Toca para reemplazar — toca un hueco relleno para cambiar el plato directamente. -• Exportar en cualquier momento — comparte tu semana aunque no esté completa; los huecos vacíos muestran "Por definir". -• Recordatorio dominical — nueva notificación para planificar la semana antes del lunes. +• Premium ahora es una suscripción mensual — platos ilimitados, planificación y exportación por 2,99 €/mes. +• Los usuarios con compra única previa conservan el acceso completo de forma automática. diff --git a/fastlane/metadata/fr-FR/promotional_text.txt b/fastlane/metadata/fr-FR/promotional_text.txt index aa9a934..78db6b4 100644 --- a/fastlane/metadata/fr-FR/promotional_text.txt +++ b/fastlane/metadata/fr-FR/promotional_text.txt @@ -1 +1 @@ -NOUVEAU v1.0.6 : Plats prioritaires, règles par jour, remplacer en un tap, et exportez même une semaine incomplète. +NOUVEAU v1.1.0 : Premium est désormais un abonnement mensuel. Les acheteurs existants conservent automatiquement leur accès complet. diff --git a/fastlane/metadata/fr-FR/release_notes.txt b/fastlane/metadata/fr-FR/release_notes.txt index 49b9408..f7b4df4 100644 --- a/fastlane/metadata/fr-FR/release_notes.txt +++ b/fastlane/metadata/fr-FR/release_notes.txt @@ -1,8 +1,4 @@ -Nouveautés dans la version 1.0.6 : +Nouveautés dans la version 1.1.0 : -• Plats prioritaires — mettez une étoile à un plat pour que l'auto-complétion le choisisse en premier. -• Règles de jours — les restrictions d'étiquettes prennent désormais en charge "jours de semaine" ou "week-end". -• Bouton auto-remplir — désormais visible directement lorsqu'il y a des créneaux vides. -• Appuyer pour remplacer — appuyez sur un créneau rempli pour changer le plat directement. -• Exporter à tout moment — partagez votre semaine même incomplète ; les créneaux vides affichent "À définir". -• Rappel du dimanche — nouvelle notification pour planifier la semaine avant le lundi. +• Premium est désormais un abonnement mensuel — plats illimités, planification et export pour 2,99 €/mois. +• Les utilisateurs ayant effectué un achat unique conservent automatiquement leur accès complet. diff --git a/fastlane/metadata/it/promotional_text.txt b/fastlane/metadata/it/promotional_text.txt index 645cedb..3fb26c5 100644 --- a/fastlane/metadata/it/promotional_text.txt +++ b/fastlane/metadata/it/promotional_text.txt @@ -1 +1 @@ -NOVITÀ v1.0.6: Piatti prioritari, regole per giorno, sostituisci con un tap ed esporta anche settimane incomplete. +NOVITÀ v1.1.0: Premium è ora un abbonamento mensile. Gli acquirenti precedenti mantengono automaticamente l'accesso completo. diff --git a/fastlane/metadata/it/release_notes.txt b/fastlane/metadata/it/release_notes.txt index c08a1b7..e35313a 100644 --- a/fastlane/metadata/it/release_notes.txt +++ b/fastlane/metadata/it/release_notes.txt @@ -1,8 +1,4 @@ -Novità nella versione 1.0.6: +Novità nella versione 1.1.0: -• Piatti prioritari — metti una stella a un piatto per farlo scegliere per primo dall'auto-completamento. -• Regole per giorni — le restrizioni dei tag ora supportano "solo giorni feriali" o "solo weekend". -• Pulsante auto-assegna — ora visibile direttamente quando ci sono slot vuoti. -• Tocca per sostituire — tocca uno slot pieno per cambiare il piatto direttamente. -• Esporta in qualsiasi momento — condividi la settimana anche se incompleta; gli slot vuoti mostrano "Da definire". -• Promemoria domenicale — nuova notifica per pianificare la settimana prima di lunedì. +• Premium è ora un abbonamento mensile — piatti illimitati, pianificazione ed esportazione a 2,99 €/mese. +• Gli utenti con acquisto singolo precedente mantengono automaticamente l'accesso completo. diff --git a/fastlane/metadata/pt-BR/promotional_text.txt b/fastlane/metadata/pt-BR/promotional_text.txt index d818d35..d8ad78f 100644 --- a/fastlane/metadata/pt-BR/promotional_text.txt +++ b/fastlane/metadata/pt-BR/promotional_text.txt @@ -1 +1 @@ -NOVIDADE 1.0.6: Pratos prioritários, regras por dia, troca com toque e exporte a semana mesmo incompleta. +NOVIDADE 1.1.0: Premium agora é uma assinatura mensal. Compradores anteriores mantêm o acesso completo automaticamente. diff --git a/fastlane/metadata/pt-BR/release_notes.txt b/fastlane/metadata/pt-BR/release_notes.txt index 1d7c095..91ff0db 100644 --- a/fastlane/metadata/pt-BR/release_notes.txt +++ b/fastlane/metadata/pt-BR/release_notes.txt @@ -1,8 +1,4 @@ -Novidades na versão 1.0.6: +Novidades na versão 1.1.0: -• Pratos prioritários — marque um prato com estrela para que a atribuição automática o escolha primeiro. -• Regras de dias — restrições de etiquetas agora suportam "apenas dias úteis" ou "apenas fim de semana". -• Botão auto-atribuir — agora visível diretamente quando há slots vazios. -• Toque para substituir — toque em um slot preenchido para trocar o prato diretamente. -• Exportar a qualquer hora — compartilhe sua semana mesmo incompleta; slots vazios mostram "A definir". -• Lembrete de domingo — nova notificação para planejar a semana antes da segunda-feira. +• Premium agora é uma assinatura mensal — pratos ilimitados, planejamento e exportação por R$ 2,99/mês. +• Usuários com compra única anterior mantêm o acesso completo automaticamente.