1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
@MainActor
@Observable
class RCStoreManager {
var hasActiveSubscription: Bool = false
var isPurchasing: Bool = false
init(apiKey: String) {
Task {
await restorePurchases()
}
}
private func updateSubscriptionStatus(from customerInfo: CustomerInfo) {
let activeEntitlements = customerInfo.entitlements.active
hasActiveSubscription = !activeEntitlements.isEmpty
}
func restorePurchases() async {
isLoading = true
defer { isLoading = false }
do {
let customerInfo = try await Purchases.shared.restorePurchases()
updateSubscriptionStatus(from: customerInfo)
} catch {
statusMessage = "Unable to restore purchases: \(error.localizedDescription)"
}
}
}
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
struct OnboardingMainView: View {
@Environment(RCStoreManager.self) private var rcStoreManager
var body: some View {
VStack {
OnboardingWelcomeView { ... }
if coordinator.currentScreen == .paywall {
Button {
dismiss()
} label: {
Text(rcStoreManager.hasActiveSubscription ? " " : "Continue without pro features")
}
.disabled(rcStoreManager.hasActiveSubscription)
}
Button {
if rcStoreManager.hasActiveSubscription {
print("Attempting to dismiss onboarding")
dismiss()
} else {
print("Trying to purchase subscription")
rcStoreManager.purchaseSelectedPackage()
}
} label: {
Text(
rcStoreManager.isPurchasing ? "Purchasing" : rcStoreManager.hasActiveSubscription ? "Close" : "Start Free Trial"
)
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation {
isShowingNextButton = true
isShowingPageIndicatorView = true
}
}
}
}
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
struct OnboardingWelcomeView: View {
let onNext: () -> Void
@State private var showingWelcomeText = false
@State private var showingTitleText = false
@State private var showingDescriptionText = false
var body: some View {
VStack {
Text("Welcome to")
.font(.title)
.opacity(showingWelcomeText ? 1 : 0)
.offset(y: showingWelcomeText ? 0 : 50)
.animation(.spring(response: 0.4, dampingFraction: 0.4, blendDuration: 0.6), value: showingWelcomeText)
Text("Redacted")
.opacity(showingTitleText ? 1 : 0)
.offset(y: showingTitleText ? 0 : 50)
.animation(.spring(response: 0.4, dampingFraction: 0.4, blendDuration: 1.0), value: showingTitleText)
Text("The ultimate companion for minipainters")
.opacity(showingDescriptionText ? 0.5 : 0)
.offset(y: showingDescriptionText ? 0 : 50)
.animation(.spring(response: 0.5, dampingFraction: 0.6), value: showingDescriptionText)
}
.onAppear {
withAnimation { showingWelcomeText = true }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation {
showingTitleText = true
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation {
showingDescriptionText = true
}
}
}
}
}