161 lines
5.7 KiB
Swift
161 lines
5.7 KiB
Swift
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))
|
|
}
|
|
}
|