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:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 +1,2 @@
|
||||
run_precheck_before_submit(false)
|
||||
force(true)
|
||||
|
||||
+2
-2
@@ -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 @@
|
||||
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.
|
||||
|
||||
@@ -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 @@
|
||||
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.
|
||||
|
||||
@@ -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 @@
|
||||
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.
|
||||
|
||||
@@ -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 @@
|
||||
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.
|
||||
|
||||
@@ -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 @@
|
||||
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.
|
||||
|
||||
@@ -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 @@
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user