Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
660974ff8f
commit
69545b55bc
@ -45,12 +45,10 @@ enum OnboardingPermissionType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Neutral progression wording for pre-permission prompts.
|
||||
/// Apple review guidance recommends labels like "Continue" or "Next".
|
||||
var buttonTitle: String {
|
||||
switch self {
|
||||
case .camera: return String(localized: "Enable Camera")
|
||||
case .microphone: return String(localized: "Enable Microphone")
|
||||
case .photoLibrary: return String(localized: "Enable Photos")
|
||||
}
|
||||
String(localized: "Continue")
|
||||
}
|
||||
|
||||
var deniedTitle: String {
|
||||
|
||||
@ -25,6 +25,9 @@ struct ProPaywallView: View {
|
||||
/// Currently selected package
|
||||
@State private var selectedPackage: Package?
|
||||
|
||||
/// Whether products are loading from RevenueCat
|
||||
@State private var isLoadingProducts = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
@ -49,10 +52,7 @@ struct ProPaywallView: View {
|
||||
}
|
||||
.font(.system(size: bodyFontSize))
|
||||
.task {
|
||||
try? await manager.loadProducts()
|
||||
if selectedPackage == nil {
|
||||
selectedPackage = preferredPackage(from: manager.availablePackages)
|
||||
}
|
||||
await loadProducts()
|
||||
}
|
||||
.onChange(of: manager.availablePackages) { _, newValue in
|
||||
if selectedPackage == nil {
|
||||
@ -117,9 +117,25 @@ struct ProPaywallView: View {
|
||||
|
||||
private var packageSelection: some View {
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
if manager.availablePackages.isEmpty {
|
||||
if isLoadingProducts {
|
||||
ProgressView()
|
||||
.padding()
|
||||
} else if manager.availablePackages.isEmpty {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
Text(String(localized: "Plans are currently unavailable. Please try again."))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Button(String(localized: "Retry")) {
|
||||
Task {
|
||||
await loadProducts()
|
||||
}
|
||||
}
|
||||
.font(.footnote.weight(.semibold))
|
||||
.foregroundStyle(AppAccent.primary)
|
||||
}
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
} else {
|
||||
ForEach(manager.availablePackages, id: \.identifier) { package in
|
||||
PackageOptionRow(
|
||||
@ -136,6 +152,15 @@ struct ProPaywallView: View {
|
||||
private var purchaseCTA: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
Button {
|
||||
guard !manager.availablePackages.isEmpty else {
|
||||
errorMessage = String(localized: "Plans are unavailable right now. Please try again.")
|
||||
showError = true
|
||||
Task {
|
||||
await loadProducts()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let selectedPackage else {
|
||||
errorMessage = String(localized: "Please select a plan.")
|
||||
showError = true
|
||||
@ -150,7 +175,12 @@ struct ProPaywallView: View {
|
||||
ProgressView()
|
||||
.tint(.white)
|
||||
}
|
||||
Text(String(localized: "Continue"))
|
||||
|
||||
Text(
|
||||
isPurchasing
|
||||
? String(localized: "Processing...")
|
||||
: String(localized: "Continue")
|
||||
)
|
||||
.font(.headline)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
@ -159,7 +189,8 @@ struct ProPaywallView: View {
|
||||
.background(AppAccent.primary)
|
||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||
}
|
||||
.disabled(isPurchasing || isRestoring || selectedPackage == nil)
|
||||
.disabled(isPurchasing || isRestoring || isLoadingProducts)
|
||||
.opacity((isPurchasing || isRestoring || isLoadingProducts) ? 0.7 : 1.0)
|
||||
|
||||
Text(String(localized: "Cancel anytime. Payment will be charged to your Apple ID."))
|
||||
.font(.caption)
|
||||
@ -238,6 +269,24 @@ struct ProPaywallView: View {
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func loadProducts() async {
|
||||
guard !isLoadingProducts else { return }
|
||||
|
||||
isLoadingProducts = true
|
||||
defer { isLoadingProducts = false }
|
||||
|
||||
do {
|
||||
try await manager.loadProducts()
|
||||
if selectedPackage == nil {
|
||||
selectedPackage = preferredPackage(from: manager.availablePackages)
|
||||
}
|
||||
} catch {
|
||||
#if DEBUG
|
||||
print("❌ [ProPaywallView] Failed to load products: \(error.localizedDescription)")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func preferredPackage(from packages: [Package]) -> Package? {
|
||||
if let annual = packages.first(where: { $0.packageType == .annual }) {
|
||||
return annual
|
||||
|
||||
@ -78,9 +78,7 @@ final class SelfieCamUITests: XCTestCase {
|
||||
let onboardingActions = [
|
||||
"Get Started",
|
||||
"Continue",
|
||||
"Enable Camera",
|
||||
"Enable Microphone",
|
||||
"Enable Photos",
|
||||
"Next",
|
||||
"Done"
|
||||
]
|
||||
let deadline = Date().addingTimeInterval(timeout)
|
||||
|
||||
67
docs/APP_REVIEW_CHECKLIST.md
Normal file
67
docs/APP_REVIEW_CHECKLIST.md
Normal file
@ -0,0 +1,67 @@
|
||||
# App Review Rejection Checklist
|
||||
|
||||
Last updated: 2026-02-12
|
||||
|
||||
## Current Status
|
||||
|
||||
- [x] Investigated rejection reasons and mapped each to implementation/App Store Connect actions.
|
||||
- [x] Fixed paywall "Continue" no-op behavior when products are unavailable.
|
||||
- File: `SelfieCam/Features/Paywall/Views/ProPaywallView.swift`
|
||||
- Added product loading state, retry UI, and explicit error handling path.
|
||||
- [x] Verified app still builds after paywall fix.
|
||||
- Debug build: success
|
||||
- Release build: success
|
||||
|
||||
## Guideline 2.1 - App Completeness (IAP not submitted)
|
||||
|
||||
- [ ] In App Store Connect, ensure all IAPs are fully configured (metadata, pricing, localization).
|
||||
- [ ] Upload required App Review screenshot for each IAP/subscription.
|
||||
- [ ] Submit all IAP products for review with the app version.
|
||||
- [ ] Upload a new app binary and submit app + IAPs together.
|
||||
|
||||
## Guideline 2.1 - App Completeness (IAP bug: Continue unresponsive)
|
||||
|
||||
- [x] Code-side paywall resiliency fix implemented (no silent button failure).
|
||||
- [ ] Run sandbox purchase tests on iPad form factor before resubmission.
|
||||
- Review device reported by Apple: iPad Air 11-inch (M3), iPadOS 26.2.1
|
||||
- [ ] Confirm Paid Apps Agreement is active.
|
||||
- Location: App Store Connect -> Agreements, Tax, and Banking
|
||||
|
||||
## Guideline 1.5 - Safety (Support URL)
|
||||
|
||||
- [ ] Update Support URL destination so it clearly provides support contact/help content.
|
||||
- Current URL: `https://topdoglabs.com/support`
|
||||
- [ ] Ensure support page includes:
|
||||
- Contact method (email or form)
|
||||
- App name(s) supported
|
||||
- Expected response time
|
||||
- Troubleshooting/help info
|
||||
- Link to privacy policy
|
||||
- [ ] Re-verify URL loads reliably in browser without requiring app login.
|
||||
|
||||
## Guideline 5.1.1 - Privacy (Permission Request Button Wording)
|
||||
|
||||
Issue reported: pre-permission custom screen uses "Enable Camera" style button text.
|
||||
|
||||
- [x] Replace pre-permission button titles with neutral progression text like "Continue" or "Next".
|
||||
- File: `SelfieCam/Features/Onboarding/Views/OnboardingPermissionView.swift`
|
||||
- Implemented: pre-permission button now uses localized `"Continue"` across camera/microphone/photo library prompts.
|
||||
- [x] Update localized strings accordingly in string catalog.
|
||||
- File: `SelfieCam/Resources/Localizable.xcstrings`
|
||||
- Existing localized `"Continue"` key is already present for supported locales (`en`, `es-MX`, `fr-CA`), so no new key entries were required.
|
||||
- [x] Update UI tests that currently look for old button labels.
|
||||
- File: `SelfieCamUITests/SelfieCamUITests.swift`
|
||||
- [ ] Verify onboarding permission flow still works for camera/microphone/photo library.
|
||||
- Note: attempted UITest validation is currently blocked by an existing project test-build issue (`Multiple commands produce ... Selfie_Cam.swiftmodule`).
|
||||
|
||||
## Resubmission Checklist
|
||||
|
||||
- [ ] Increment build number and archive a new build.
|
||||
- [ ] Upload new build to App Store Connect.
|
||||
- [ ] Attach submitted IAPs to the app version.
|
||||
- [ ] Add App Review Notes summarizing fixes:
|
||||
- Paywall button behavior fixed
|
||||
- IAPs submitted with required metadata/screenshots
|
||||
- Support URL updated
|
||||
- Permission request button wording updated
|
||||
- [ ] Submit for review.
|
||||
Loading…
Reference in New Issue
Block a user