|
|
@@ -19,6 +19,7 @@ struct PremiumFeaturesView: View {
|
|
|
|
|
|
@EnvironmentObject private var premiumStore: PremiumStore
|
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
+ @Environment(\.colorScheme) private var colorScheme
|
|
|
@State private var selectedPlan: PlanOption = .perpetual
|
|
|
@State private var showingSuccessAlert = false
|
|
|
|
|
|
@@ -57,30 +58,19 @@ struct PremiumFeaturesView: View {
|
|
|
var body: some View {
|
|
|
ZStack {
|
|
|
LinearGradient(
|
|
|
- colors: [Color.black.opacity(0.9), Color(red: 0.08, green: 0.1, blue: 0.14)],
|
|
|
+ colors: backgroundGradientColors,
|
|
|
startPoint: .topLeading,
|
|
|
endPoint: .bottomTrailing
|
|
|
)
|
|
|
.ignoresSafeArea()
|
|
|
|
|
|
- Rectangle()
|
|
|
- .fill(
|
|
|
- RadialGradient(
|
|
|
- colors: [Color.blue.opacity(0.25), .clear],
|
|
|
- center: .bottomTrailing,
|
|
|
- startRadius: 40,
|
|
|
- endRadius: 380
|
|
|
- )
|
|
|
- )
|
|
|
- .ignoresSafeArea()
|
|
|
-
|
|
|
ScrollView {
|
|
|
VStack(alignment: .leading, spacing: 14) {
|
|
|
HStack {
|
|
|
Button(action: { dismissPremiumUI() }) {
|
|
|
Image(systemName: "xmark")
|
|
|
.font(.system(size: 13, weight: .semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.88))
|
|
|
+ .foregroundStyle(primaryTextColor)
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
|
|
|
@@ -91,12 +81,12 @@ struct PremiumFeaturesView: View {
|
|
|
} label: {
|
|
|
Text("Restore")
|
|
|
.font(.system(size: 11, weight: .semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.9))
|
|
|
+ .foregroundStyle(primaryTextColor)
|
|
|
.padding(.horizontal, 8)
|
|
|
.padding(.vertical, 3)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 7, style: .continuous)
|
|
|
- .fill(Color.white.opacity(0.16))
|
|
|
+ .fill(chipFillColor)
|
|
|
)
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
@@ -105,7 +95,7 @@ struct PremiumFeaturesView: View {
|
|
|
|
|
|
Text("Upgrade to unlock all\nPremium Features")
|
|
|
.font(.system(size: 20, weight: .bold))
|
|
|
- .foregroundStyle(.white)
|
|
|
+ .foregroundStyle(primaryTextColor)
|
|
|
.lineSpacing(1)
|
|
|
|
|
|
if let err = premiumStore.loadError {
|
|
|
@@ -133,12 +123,12 @@ struct PremiumFeaturesView: View {
|
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
|
Text("Store products not loaded")
|
|
|
.font(.system(size: 14, weight: .bold))
|
|
|
- .foregroundStyle(.white)
|
|
|
+ .foregroundStyle(primaryTextColor)
|
|
|
Text(
|
|
|
"Prices and checkout need product metadata from App Store Connect, or a StoreKit Configuration file when running from Xcode. Product IDs must match exactly."
|
|
|
)
|
|
|
.font(.system(size: 11, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.85))
|
|
|
+ .foregroundStyle(secondaryTextColor)
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
Button {
|
|
|
Task { await premiumStore.loadProducts() }
|
|
|
@@ -220,15 +210,15 @@ struct PremiumFeaturesView: View {
|
|
|
Spacer()
|
|
|
Text("Privacy Policy")
|
|
|
.font(.system(size: 11, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.7))
|
|
|
+ .foregroundStyle(secondaryTextColor)
|
|
|
Spacer()
|
|
|
Text("Support")
|
|
|
.font(.system(size: 11, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.7))
|
|
|
+ .foregroundStyle(secondaryTextColor)
|
|
|
Spacer()
|
|
|
Text("Terms of Service")
|
|
|
.font(.system(size: 11, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.7))
|
|
|
+ .foregroundStyle(secondaryTextColor)
|
|
|
Spacer()
|
|
|
}
|
|
|
.padding(.top, 2)
|
|
|
@@ -262,6 +252,25 @@ struct PremiumFeaturesView: View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private var backgroundGradientColors: [Color] {
|
|
|
+ if colorScheme == .dark {
|
|
|
+ return [Color.black.opacity(0.9), Color(red: 0.08, green: 0.1, blue: 0.14)]
|
|
|
+ }
|
|
|
+ return [Color.white.opacity(0.96), Color(red: 0.92, green: 0.95, blue: 1.0)]
|
|
|
+ }
|
|
|
+
|
|
|
+ private var primaryTextColor: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.92) : .primary.opacity(0.95)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var secondaryTextColor: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.72) : .primary.opacity(0.70)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var chipFillColor: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.16) : .black.opacity(0.08)
|
|
|
+ }
|
|
|
+
|
|
|
private func featureCard(title: String, subtitle: String, color: Color) -> some View {
|
|
|
HStack(alignment: .top, spacing: 10) {
|
|
|
Circle()
|
|
|
@@ -276,11 +285,11 @@ struct PremiumFeaturesView: View {
|
|
|
VStack(alignment: .leading, spacing: 3) {
|
|
|
Text(title)
|
|
|
.font(.system(size: 11.5, weight: .bold))
|
|
|
- .foregroundStyle(.white)
|
|
|
+ .foregroundStyle(primaryTextColor)
|
|
|
.lineLimit(1)
|
|
|
Text(subtitle)
|
|
|
.font(.system(size: 9.5, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.85))
|
|
|
+ .foregroundStyle(secondaryTextColor)
|
|
|
.lineLimit(2)
|
|
|
}
|
|
|
Spacer(minLength: 0)
|
|
|
@@ -289,19 +298,29 @@ struct PremiumFeaturesView: View {
|
|
|
.frame(maxWidth: .infinity, minHeight: 66, alignment: .leading)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
- .fill(Color.white.opacity(0.14))
|
|
|
+ .fill(colorScheme == .dark ? Color.white.opacity(0.14) : Color.black.opacity(0.05))
|
|
|
)
|
|
|
}
|
|
|
|
|
|
private func priceCard(title: String, price: String, subtitle: String, badgeText: String?, isSelected: Bool) -> some View {
|
|
|
- HStack(alignment: .center) {
|
|
|
+ let cardStroke: Color = {
|
|
|
+ if isSelected { return Color.blue.opacity(0.95) }
|
|
|
+ return colorScheme == .dark ? Color.white.opacity(0.4) : Color.black.opacity(0.18)
|
|
|
+ }()
|
|
|
+ let cardFill: Color = {
|
|
|
+ if isSelected { return Color.blue.opacity(colorScheme == .dark ? 0.12 : 0.10) }
|
|
|
+ return colorScheme == .dark ? Color.white.opacity(0.05) : Color.black.opacity(0.025)
|
|
|
+ }()
|
|
|
+ let cardStrokeWidth: CGFloat = isSelected ? 2 : 1.5
|
|
|
+
|
|
|
+ return HStack(alignment: .center) {
|
|
|
VStack(alignment: .leading, spacing: 5) {
|
|
|
Text(title)
|
|
|
.font(.system(size: 12, weight: .bold))
|
|
|
- .foregroundStyle(.white)
|
|
|
+ .foregroundStyle(primaryTextColor)
|
|
|
Text(subtitle)
|
|
|
.font(.system(size: 7.5, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.72))
|
|
|
+ .foregroundStyle(secondaryTextColor)
|
|
|
}
|
|
|
|
|
|
Spacer()
|
|
|
@@ -309,7 +328,7 @@ struct PremiumFeaturesView: View {
|
|
|
VStack(alignment: .trailing, spacing: 7) {
|
|
|
Text(price)
|
|
|
.font(.system(size: 10, weight: .bold))
|
|
|
- .foregroundStyle(.white)
|
|
|
+ .foregroundStyle(primaryTextColor)
|
|
|
.minimumScaleFactor(0.7)
|
|
|
.lineLimit(1)
|
|
|
if let badgeText {
|
|
|
@@ -328,10 +347,10 @@ struct PremiumFeaturesView: View {
|
|
|
.padding(10)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
|
- .stroke(isSelected ? Color.blue.opacity(0.95) : Color.white.opacity(0.4), lineWidth: isSelected ? 2 : 1.5)
|
|
|
+ .stroke(cardStroke, lineWidth: cardStrokeWidth)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
|
- .fill(isSelected ? Color.blue.opacity(0.12) : Color.white.opacity(0.05))
|
|
|
+ .fill(cardFill)
|
|
|
)
|
|
|
)
|
|
|
.contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|