Improve subscription UI and fix trial date handling
Lock Screen: - Add light/dark mode support with adaptive colors - Make passcode text tappable to trigger authentication Trial Date Fixes: - Fix IAPManager to read firstLaunchDate directly from UserDefaults - Add expiration date check (> Date()) before showing "expires in" text - Show "Trial expired" when trial end date is in the past - Disable subscription bypass in DEBUG mode for testing Month/Year Subscribe Prompts: - Redesign with gradient icons and compelling copy - Add fade mask (100% at top to 0% at 50%) for content behind - Position subscribe overlay on bottom half of screen - Month: purple/pink theme with calendar icon - Year: orange/pink theme with chart icon 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,15 +21,20 @@ struct IAPWarningView: View {
|
||||
Image(systemName: "clock")
|
||||
.foregroundColor(.orange)
|
||||
|
||||
Text(String(localized: "iap_warning_view_title"))
|
||||
.font(.body)
|
||||
.foregroundColor(textColor)
|
||||
if let expirationDate = iapManager.trialExpirationDate, expirationDate > Date() {
|
||||
Text(String(localized: "iap_warning_view_title"))
|
||||
.font(.body)
|
||||
.foregroundColor(textColor)
|
||||
|
||||
if let expirationDate = iapManager.trialExpirationDate {
|
||||
Text(expirationDate, style: .relative)
|
||||
.font(.body)
|
||||
.bold()
|
||||
.foregroundColor(.orange)
|
||||
} else {
|
||||
Text(String(localized: "purchase_view_trial_expired"))
|
||||
.font(.body)
|
||||
.bold()
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ struct MoodParticle: Identifiable {
|
||||
// MARK: - Aurora Gradient Background
|
||||
|
||||
struct AuroraBackground: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@State private var animateGradient = false
|
||||
|
||||
private let moodColors: [Color] = [
|
||||
@@ -33,14 +34,20 @@ struct AuroraBackground: View {
|
||||
Color(hex: "ff453a"), // horrible - red
|
||||
]
|
||||
|
||||
private var isDark: Bool { colorScheme == .dark }
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Base dark gradient
|
||||
// Base gradient - adapts to color scheme
|
||||
LinearGradient(
|
||||
colors: [
|
||||
colors: isDark ? [
|
||||
Color(hex: "0a0a0f"),
|
||||
Color(hex: "12121a"),
|
||||
Color(hex: "0d0d14")
|
||||
] : [
|
||||
Color(hex: "f8f9fa"),
|
||||
Color(hex: "e9ecef"),
|
||||
Color(hex: "f1f3f5")
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
@@ -49,8 +56,8 @@ struct AuroraBackground: View {
|
||||
// Aurora layer 1 - green/blue
|
||||
EllipticalGradient(
|
||||
colors: [
|
||||
moodColors[0].opacity(0.3),
|
||||
moodColors[2].opacity(0.15),
|
||||
moodColors[0].opacity(isDark ? 0.3 : 0.2),
|
||||
moodColors[2].opacity(isDark ? 0.15 : 0.1),
|
||||
.clear
|
||||
],
|
||||
center: .topLeading,
|
||||
@@ -63,8 +70,8 @@ struct AuroraBackground: View {
|
||||
// Aurora layer 2 - yellow/orange
|
||||
EllipticalGradient(
|
||||
colors: [
|
||||
moodColors[1].opacity(0.2),
|
||||
moodColors[3].opacity(0.1),
|
||||
moodColors[1].opacity(isDark ? 0.2 : 0.15),
|
||||
moodColors[3].opacity(isDark ? 0.1 : 0.08),
|
||||
.clear
|
||||
],
|
||||
center: .bottomTrailing,
|
||||
@@ -77,7 +84,7 @@ struct AuroraBackground: View {
|
||||
// Aurora layer 3 - subtle red accent
|
||||
RadialGradient(
|
||||
colors: [
|
||||
moodColors[4].opacity(0.15),
|
||||
moodColors[4].opacity(isDark ? 0.15 : 0.1),
|
||||
.clear
|
||||
],
|
||||
center: UnitPoint(x: 0.8, y: 0.3),
|
||||
@@ -89,7 +96,7 @@ struct AuroraBackground: View {
|
||||
|
||||
// Noise texture overlay
|
||||
Rectangle()
|
||||
.fill(.white.opacity(0.015))
|
||||
.fill((isDark ? Color.white : Color.black).opacity(0.015))
|
||||
.background(
|
||||
Canvas { context, size in
|
||||
for _ in 0..<1000 {
|
||||
@@ -98,7 +105,7 @@ struct AuroraBackground: View {
|
||||
let opacity = Double.random(in: 0.01...0.04)
|
||||
context.fill(
|
||||
Path(ellipseIn: CGRect(x: x, y: y, width: 1, height: 1)),
|
||||
with: .color(.white.opacity(opacity))
|
||||
with: .color((isDark ? Color.white : Color.black).opacity(opacity))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -190,6 +197,7 @@ struct FloatingAnimation: ViewModifier {
|
||||
// MARK: - Central Breathing Orb
|
||||
|
||||
struct BreathingOrb: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@State private var breathe = false
|
||||
@State private var rotate = false
|
||||
|
||||
@@ -201,6 +209,8 @@ struct BreathingOrb: View {
|
||||
Color(hex: "ff453a"),
|
||||
]
|
||||
|
||||
private var isDark: Bool { colorScheme == .dark }
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Outer glow
|
||||
@@ -215,7 +225,7 @@ struct BreathingOrb: View {
|
||||
)
|
||||
.frame(width: 180, height: 180)
|
||||
.blur(radius: 40)
|
||||
.opacity(0.6)
|
||||
.opacity(isDark ? 0.6 : 0.5)
|
||||
.scaleEffect(breathe ? 1.2 : 0.9)
|
||||
.rotationEffect(.degrees(rotate ? 360 : 0))
|
||||
|
||||
@@ -229,7 +239,7 @@ struct BreathingOrb: View {
|
||||
)
|
||||
.frame(width: 120, height: 120)
|
||||
.blur(radius: 20)
|
||||
.opacity(0.8)
|
||||
.opacity(isDark ? 0.8 : 0.6)
|
||||
.scaleEffect(breathe ? 1.1 : 0.95)
|
||||
.rotationEffect(.degrees(rotate ? -360 : 0))
|
||||
|
||||
@@ -237,10 +247,14 @@ struct BreathingOrb: View {
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
colors: [
|
||||
colors: isDark ? [
|
||||
.white.opacity(0.9),
|
||||
.white.opacity(0.3),
|
||||
.clear
|
||||
] : [
|
||||
.white,
|
||||
.white.opacity(0.6),
|
||||
.clear
|
||||
],
|
||||
center: .center,
|
||||
startRadius: 0,
|
||||
@@ -249,12 +263,13 @@ struct BreathingOrb: View {
|
||||
)
|
||||
.frame(width: 80, height: 80)
|
||||
.scaleEffect(breathe ? 1.05 : 0.98)
|
||||
.shadow(color: .black.opacity(isDark ? 0 : 0.1), radius: 10)
|
||||
|
||||
// Glossy highlight
|
||||
Ellipse()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [.white.opacity(0.4), .clear],
|
||||
colors: [.white.opacity(isDark ? 0.4 : 0.8), .clear],
|
||||
startPoint: .top,
|
||||
endPoint: .center
|
||||
)
|
||||
@@ -283,6 +298,7 @@ struct BreathingOrb: View {
|
||||
// MARK: - Glassmorphic Button
|
||||
|
||||
struct GlassButton: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
let icon: String
|
||||
let title: String
|
||||
let action: () -> Void
|
||||
@@ -290,6 +306,10 @@ struct GlassButton: View {
|
||||
@State private var isPressed = false
|
||||
@State private var pulse = false
|
||||
|
||||
private var isDark: Bool { colorScheme == .dark }
|
||||
private var foregroundColor: Color { isDark ? .white : .primary }
|
||||
private var accentOpacity: Double { isDark ? 0.15 : 0.1 }
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 14) {
|
||||
@@ -297,24 +317,24 @@ struct GlassButton: View {
|
||||
// Pulse ring
|
||||
Circle()
|
||||
.stroke(lineWidth: 2)
|
||||
.foregroundColor(.white.opacity(0.3))
|
||||
.foregroundColor(foregroundColor.opacity(0.3))
|
||||
.frame(width: 44, height: 44)
|
||||
.scaleEffect(pulse ? 1.3 : 1)
|
||||
.opacity(pulse ? 0 : 0.6)
|
||||
|
||||
// Icon background
|
||||
Circle()
|
||||
.fill(.white.opacity(0.15))
|
||||
.fill(foregroundColor.opacity(accentOpacity))
|
||||
.frame(width: 44, height: 44)
|
||||
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(foregroundColor)
|
||||
}
|
||||
|
||||
Text(title)
|
||||
.font(.system(size: 17, weight: .semibold, design: .rounded))
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(foregroundColor)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 18)
|
||||
@@ -324,16 +344,15 @@ struct GlassButton: View {
|
||||
// Glass effect
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.fill(.ultraThinMaterial)
|
||||
.opacity(0.8)
|
||||
|
||||
// Border gradient
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
.white.opacity(0.4),
|
||||
.white.opacity(0.1),
|
||||
.white.opacity(0.2)
|
||||
foregroundColor.opacity(0.3),
|
||||
foregroundColor.opacity(0.1),
|
||||
foregroundColor.opacity(0.15)
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
@@ -341,11 +360,11 @@ struct GlassButton: View {
|
||||
lineWidth: 1
|
||||
)
|
||||
|
||||
// Inner shadow
|
||||
// Inner highlight
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [.white.opacity(0.1), .clear],
|
||||
colors: [foregroundColor.opacity(0.08), .clear],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
@@ -382,11 +401,16 @@ struct GlassButton: View {
|
||||
// MARK: - Main Lock Screen View
|
||||
|
||||
struct LockScreenView: View {
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@ObservedObject var authManager: BiometricAuthManager
|
||||
@State private var showError = false
|
||||
@State private var showContent = false
|
||||
|
||||
private var isDark: Bool { colorScheme == .dark }
|
||||
private var primaryText: Color { isDark ? .white : .primary }
|
||||
private var secondaryText: Color { isDark ? .white.opacity(0.7) : .secondary }
|
||||
private var tertiaryText: Color { isDark ? .white.opacity(0.5) : .secondary.opacity(0.8) }
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Aurora background
|
||||
@@ -411,11 +435,11 @@ struct LockScreenView: View {
|
||||
VStack(spacing: 12) {
|
||||
Text("Your Feelings")
|
||||
.font(.system(size: 32, weight: .light, design: .serif))
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(primaryText)
|
||||
|
||||
Text("are safe here")
|
||||
.font(.system(size: 32, weight: .ultraLight, design: .serif))
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.foregroundColor(secondaryText)
|
||||
}
|
||||
.opacity(showContent ? 1 : 0)
|
||||
.offset(y: showContent ? 0 : 20)
|
||||
@@ -425,7 +449,7 @@ struct LockScreenView: View {
|
||||
|
||||
Text("Authenticate to continue your journey")
|
||||
.font(.system(size: 14, weight: .regular, design: .rounded))
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
.foregroundColor(tertiaryText)
|
||||
.opacity(showContent ? 1 : 0)
|
||||
|
||||
Spacer()
|
||||
@@ -447,13 +471,23 @@ struct LockScreenView: View {
|
||||
.offset(y: showContent ? 0 : 30)
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
// Passcode hint
|
||||
// Passcode button - tappable to authenticate with passcode
|
||||
if authManager.canUseDevicePasscode {
|
||||
Text("Or use your device passcode")
|
||||
.font(.system(size: 12, weight: .regular, design: .rounded))
|
||||
.foregroundColor(.white.opacity(0.35))
|
||||
.padding(.top, 16)
|
||||
.opacity(showContent ? 1 : 0)
|
||||
Button {
|
||||
Task {
|
||||
let success = await authManager.authenticate()
|
||||
if !success {
|
||||
showError = true
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Or use your device passcode")
|
||||
.font(.system(size: 13, weight: .medium, design: .rounded))
|
||||
.foregroundColor(isDark ? .white.opacity(0.5) : .accentColor)
|
||||
}
|
||||
.disabled(authManager.isAuthenticating)
|
||||
.padding(.top, 16)
|
||||
.opacity(showContent ? 1 : 0)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -490,12 +524,12 @@ struct LockScreenView: View {
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview("Lock Screen - Face ID") {
|
||||
#Preview("Lock Screen - Dark") {
|
||||
LockScreenView(authManager: BiometricAuthManager())
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
#Preview("Lock Screen - Touch ID") {
|
||||
#Preview("Lock Screen - Light") {
|
||||
LockScreenView(authManager: BiometricAuthManager())
|
||||
.preferredColorScheme(.dark)
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
|
||||
@@ -109,6 +109,21 @@ struct MonthView: View {
|
||||
)
|
||||
}
|
||||
.scrollDisabled(iapManager.shouldShowPaywall)
|
||||
.mask(
|
||||
// Fade effect when paywall should show: 100% at top, 0% halfway down
|
||||
iapManager.shouldShowPaywall ?
|
||||
AnyView(
|
||||
LinearGradient(
|
||||
gradient: Gradient(stops: [
|
||||
.init(color: .black, location: 0),
|
||||
.init(color: .black, location: 0.3),
|
||||
.init(color: .clear, location: 0.5)
|
||||
]),
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
) : AnyView(Color.black)
|
||||
)
|
||||
}
|
||||
|
||||
// Hidden text to trigger updates when custom tint changes
|
||||
@@ -116,27 +131,72 @@ struct MonthView: View {
|
||||
.hidden()
|
||||
|
||||
if iapManager.shouldShowPaywall {
|
||||
// Paywall overlay - tap to show subscription store
|
||||
Color.black.opacity(0.3)
|
||||
.ignoresSafeArea()
|
||||
.onTapGesture {
|
||||
showSubscriptionStore = true
|
||||
// Premium month history prompt - bottom half
|
||||
VStack(spacing: 20) {
|
||||
// Icon
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [.purple.opacity(0.2), .pink.opacity(0.2)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.frame(width: 80, height: 80)
|
||||
|
||||
Image(systemName: "calendar.badge.clock")
|
||||
.font(.title)
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: [.purple, .pink],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
// Text
|
||||
VStack(spacing: 10) {
|
||||
Text("Explore Your Mood History")
|
||||
.font(.title3.weight(.bold))
|
||||
.foregroundColor(textColor)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text("See your complete monthly journey. Track patterns and understand what shapes your days.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(textColor.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
|
||||
// Subscribe button
|
||||
Button {
|
||||
showSubscriptionStore = true
|
||||
} label: {
|
||||
Text(String(localized: "subscription_required_button"))
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(RoundedRectangle(cornerRadius: 10).fill(Color.pink))
|
||||
HStack {
|
||||
Image(systemName: "calendar")
|
||||
Text("Unlock Full History")
|
||||
}
|
||||
.font(.headline.weight(.bold))
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
.background(
|
||||
LinearGradient(
|
||||
colors: [.purple, .pink],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
.padding()
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(theme.currentTheme.bg)
|
||||
.frame(maxHeight: .infinity, alignment: .bottom)
|
||||
} else if iapManager.shouldShowTrialWarning {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
@@ -180,8 +180,12 @@ struct PurchaseButtonView: View {
|
||||
Image(systemName: "clock")
|
||||
.foregroundColor(.orange)
|
||||
|
||||
if let expirationDate = iapManager.trialExpirationDate {
|
||||
if let expirationDate = iapManager.trialExpirationDate, expirationDate > Date() {
|
||||
Text("\(Text(String(localized: "purchase_view_trial_expires_in")).foregroundColor(textColor)) \(Text(expirationDate, style: .relative).foregroundColor(.orange).bold())")
|
||||
} else {
|
||||
Text(String(localized: "purchase_view_trial_expired"))
|
||||
.foregroundColor(.orange)
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
.font(.body)
|
||||
|
||||
@@ -95,7 +95,7 @@ struct UpgradeBannerView: View {
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(.orange)
|
||||
|
||||
if let expirationDate = trialExpirationDate {
|
||||
if let expirationDate = trialExpirationDate, expirationDate > Date() {
|
||||
Text("\(Text("Trial expires in ").font(.subheadline.weight(.medium)).foregroundColor(textColor.opacity(0.8)))\(Text(expirationDate, style: .relative).font(.subheadline.weight(.bold)).foregroundColor(.orange))")
|
||||
} else {
|
||||
Text("Trial expired")
|
||||
|
||||
@@ -66,14 +66,14 @@ struct YearView: View {
|
||||
}
|
||||
.scrollDisabled(iapManager.shouldShowPaywall)
|
||||
.mask(
|
||||
// Fade effect when paywall should show: 100% at top, 0% at bottom
|
||||
// Fade effect when paywall should show: 100% at top, 0% halfway down
|
||||
iapManager.shouldShowPaywall ?
|
||||
AnyView(
|
||||
LinearGradient(
|
||||
gradient: Gradient(stops: [
|
||||
.init(color: .black, location: 0),
|
||||
.init(color: .black, location: 0.3),
|
||||
.init(color: .clear, location: 1.0)
|
||||
.init(color: .clear, location: 0.5)
|
||||
]),
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
@@ -83,26 +83,72 @@ struct YearView: View {
|
||||
}
|
||||
|
||||
if iapManager.shouldShowPaywall {
|
||||
VStack {
|
||||
Spacer()
|
||||
VStack(spacing: 16) {
|
||||
Text("Subscribe to see your full year")
|
||||
.font(.headline)
|
||||
.foregroundColor(textColor)
|
||||
// Premium year overview prompt - bottom half
|
||||
VStack(spacing: 20) {
|
||||
// Icon
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [.orange.opacity(0.2), .pink.opacity(0.2)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.frame(width: 80, height: 80)
|
||||
|
||||
Button {
|
||||
showSubscriptionStore = true
|
||||
} label: {
|
||||
Text(String(localized: "subscription_required_button"))
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(RoundedRectangle(cornerRadius: 10).fill(Color.pink))
|
||||
}
|
||||
Image(systemName: "chart.bar.xaxis")
|
||||
.font(.title)
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: [.orange, .pink],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
|
||||
// Text
|
||||
VStack(spacing: 10) {
|
||||
Text("See Your Year at a Glance")
|
||||
.font(.title3.weight(.bold))
|
||||
.foregroundColor(textColor)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text("Discover your emotional rhythm across the year. Spot trends and celebrate how far you've come.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(textColor.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
|
||||
// Subscribe button
|
||||
Button {
|
||||
showSubscriptionStore = true
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "chart.bar.fill")
|
||||
Text("Unlock Year Overview")
|
||||
}
|
||||
.font(.headline.weight(.bold))
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
.background(
|
||||
LinearGradient(
|
||||
colors: [.orange, .pink],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(theme.currentTheme.bg)
|
||||
.frame(maxHeight: .infinity, alignment: .bottom)
|
||||
} else if iapManager.shouldShowTrialWarning {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
Reference in New Issue
Block a user