Unit tests und nudging screen
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
/// Nudge sheet asking the user to support development.
|
||||
/// Shown automatically 3 days after first launch, then every 6 months.
|
||||
struct SupportNudgeView: View {
|
||||
@Environment(MAStoreManager.self) private var storeManager
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
// MARK: - Hero
|
||||
VStack(spacing: 16) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.orange, Color.pink],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.frame(width: 80, height: 80)
|
||||
.shadow(color: .orange.opacity(0.4), radius: 16, y: 6)
|
||||
|
||||
Image(systemName: "heart.fill")
|
||||
.font(.system(size: 36))
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
.padding(.top, 32)
|
||||
|
||||
Text("Keep Mobile MA Growing")
|
||||
.font(.title2.weight(.bold))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text("Mobile MA is a free, passion-driven app. If it brings music to your life, a small donation helps keep it alive and growing.")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
.padding(.bottom, 32)
|
||||
|
||||
// MARK: - Tiers
|
||||
VStack(spacing: 12) {
|
||||
if storeManager.products.isEmpty {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 32)
|
||||
} else {
|
||||
ForEach(storeManager.products, id: \.id) { product in
|
||||
TierRow(product: product, storeManager: storeManager, isPresented: $isPresented)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
// MARK: - Dismiss
|
||||
Button {
|
||||
isPresented = false
|
||||
} label: {
|
||||
Text("Maybe Later")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
isPresented = false
|
||||
} label: {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.title3)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
if storeManager.products.isEmpty {
|
||||
await storeManager.loadProducts()
|
||||
}
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tier Row
|
||||
|
||||
private struct TierRow: View {
|
||||
let product: Product
|
||||
let storeManager: MAStoreManager
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
private var accentColor: Color {
|
||||
switch product.id {
|
||||
case "donatesong": return .teal
|
||||
case "donatealbum": return .orange
|
||||
case "donateanthology": return .purple
|
||||
default: return .pink
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 14) {
|
||||
// Icon
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(accentColor.opacity(0.15))
|
||||
.frame(width: 48, height: 48)
|
||||
Image(systemName: storeManager.iconName(for: product))
|
||||
.font(.title3)
|
||||
.foregroundStyle(accentColor)
|
||||
}
|
||||
|
||||
// Info
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(storeManager.tierName(for: product))
|
||||
.font(.body.weight(.medium))
|
||||
Text(product.description)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Buy button
|
||||
Button {
|
||||
Task {
|
||||
await storeManager.purchase(product)
|
||||
if case .success = storeManager.purchaseResult {
|
||||
isPresented = false
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text(product.displayPrice)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 8)
|
||||
.background(accentColor.opacity(0.15))
|
||||
.foregroundStyle(accentColor)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(storeManager.isPurchasing)
|
||||
}
|
||||
.padding(14)
|
||||
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user