1.1.1 (46): subscription IAP, export compliance, metadata

- Switch premium to AutoRenewable subscription (com.mealmood.premium.monthly.sub);
  legacy one-time purchasers retain lifetime access via StoreManager fallback
- Add ITSAppUsesNonExemptEncryption=false to Info.plist (no custom encryption)
- Update storekit config with both legacy NonConsumable and new subscription
- Bump version 1.1.1 build 46; update fastlane metadata and release notes (6 langs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-05-18 11:28:14 +02:00
parent 7da7995538
commit 89b35dc4ae
20 changed files with 74 additions and 86 deletions
+1 -1
View File
@@ -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)
+4 -4
View File
@@ -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;
+16 -2
View File
@@ -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"
+4 -2
View File
@@ -15,11 +15,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.6</string>
<string>1.1.1</string>
<key>CFBundleVersion</key>
<string>41</string>
<string>46</string>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-1549720748100858~9985112590</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCalendarsFullAccessUsageDescription</key>
+20 -25
View File
@@ -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<String> = [monthlyProductId, "\(bundleId).premium.monthly.sub"]
// Legacy one-time purchases grant lifetime access no expiration check needed.
let legacyIds: Set<String> = [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
}
+2 -2
View File
@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0.6</string>
<string>1.1.1</string>
<key>CFBundleVersion</key>
<string>41</string>
<string>46</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
+1
View File
@@ -1 +1,2 @@
run_precheck_before_submit(false)
force(true)
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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.
+3 -7
View File
@@ -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.
+1 -1
View File
@@ -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.
+3 -7
View File
@@ -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.
+1 -1
View File
@@ -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.
+3 -7
View File
@@ -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.
+1 -1
View File
@@ -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.
+3 -7
View File
@@ -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.
+1 -1
View File
@@ -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.
+3 -7
View File
@@ -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.
+1 -1
View File
@@ -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.
+3 -7
View File
@@ -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.