Files
upstream-affine/packages/frontend/apps/ios/App/App/Onboarding/AppPaywallView.swift
upstream-mirror 702cc698a7
Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
2026-09-16 04:01:20 +05:00

170 lines
4.9 KiB
Swift

import AffinePaywall
import AffineResources
import SwiftUI
struct AppPaywallRootView: View {
@Environment(\.colorScheme) private var colorScheme
@ObservedObject var bridge: NativePaywallBridge
let initialPlan: AppPaywallPlan
let onPurchase: (AppPaywallPlan) -> Void
let onRestorePurchases: () -> Void
let onClose: () -> Void
private var palette: AppPaywallPalette {
AppPaywallPalette(colorScheme: colorScheme)
}
var body: some View {
GeometryReader { geometry in
let layout = AppPaywallLayout(
size: geometry.size,
safeAreaInsets: geometry.safeAreaInsets
)
ZStack {
OnboardingBackground(isIntroPage: false)
palette.backgroundOverlay.ignoresSafeArea()
ScrollView(.vertical, showsIndicators: false) {
AppPaywallPage(
bridge: bridge,
initialPlan: initialPlan,
layout: layout,
palette: palette,
onPurchase: onPurchase,
onRestorePurchases: onRestorePurchases,
onClose: onClose
)
.frame(minHeight: layout.pageMinHeight, alignment: .top)
}
.scrollBounceBehavior(.basedOnSize)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.foregroundStyle(palette.primaryText)
}
.alert(
"Paywall",
isPresented: Binding(
get: { bridge.errorMessage != nil },
set: { isPresented in
if !isPresented {
bridge.errorMessage = nil
}
}
)
) {
Button("OK", role: .cancel) {}
} message: {
Text(bridge.errorMessage ?? "")
}
}
}
private struct AppPaywallPage: View {
@Environment(\.openURL) private var openURL
@ObservedObject var bridge: NativePaywallBridge
let initialPlan: AppPaywallPlan
let layout: AppPaywallLayout
let palette: AppPaywallPalette
let onPurchase: (AppPaywallPlan) -> Void
let onRestorePurchases: () -> Void
let onClose: () -> Void
private let plans = AppPaywallPlan.allCases
@State private var selectedPlan: AppPaywallPlan
init(
bridge: NativePaywallBridge,
initialPlan: AppPaywallPlan,
layout: AppPaywallLayout,
palette: AppPaywallPalette,
onPurchase: @escaping (AppPaywallPlan) -> Void,
onRestorePurchases: @escaping () -> Void,
onClose: @escaping () -> Void
) {
self.bridge = bridge
self.initialPlan = initialPlan
self.layout = layout
self.palette = palette
self.onPurchase = onPurchase
self.onRestorePurchases = onRestorePurchases
self.onClose = onClose
_selectedPlan = State(initialValue: initialPlan)
}
var body: some View {
VStack(spacing: layout.sectionSpacing) {
Text("Choose your plan")
.font(.system(size: layout.titleFontSize, weight: .bold))
.frame(maxWidth: .infinity, alignment: .leading)
Picker("Plan", selection: $selectedPlan) {
ForEach(plans, id: \.self) { plan in
Text(LocalizedStringKey(plan.headerName)).tag(plan)
}
}
.pickerStyle(.segmented)
AppPaywallCard(
plan: selectedPlan,
priceInfo: bridge.priceInfo(for: selectedPlan.planKind),
layout: layout,
palette: palette
)
Text("Subscriptions renew automatically until canceled.")
.font(.footnote)
.foregroundStyle(palette.secondaryText)
.multilineTextAlignment(.center)
PrimaryButton(
title: selectedPlan.buttonTitle,
isLoading: bridge.isLoading || bridge.isProcessing,
isEnabled: bridge.isReady,
fontSize: layout.buttonFontSize,
height: layout.buttonHeight
) {
triggerPaywallHaptic()
onPurchase(selectedPlan)
}
Button("Not now", action: onClose)
.font(.system(size: 17, weight: .medium))
.foregroundStyle(palette.secondaryText)
.frame(maxWidth: .infinity, minHeight: 44)
.buttonStyle(.plain)
AppPaywallLegalLinks(
palette: palette,
onOpenTerms: { openLegalURL("https://affine.pro/terms") },
onOpenPrivacy: { openLegalURL("https://affine.pro/privacy") },
onOpenSubscriptionTerms: { openLegalURL("https://affine.pro/terms/#subscription") },
onRestore: onRestorePurchases
)
}
.padding(.horizontal, layout.horizontalPadding)
.padding(.top, layout.topPadding)
.padding(.bottom, layout.bottomPadding)
.frame(maxWidth: .infinity, minHeight: layout.pageMinHeight, alignment: .top)
.onAppear {
bridge.selectPlan(initialPlan.planKind)
}
.onChange(of: selectedPlan) { plan in
bridge.selectPlan(plan.planKind)
}
}
private func openLegalURL(_ string: String) {
guard let url = URL(string: string) else { return }
openURL(url)
}
private func triggerPaywallHaptic() {
let generator = UIImpactFeedbackGenerator(style: .light)
generator.prepare()
generator.impactOccurred()
}
}