208 lines
6.9 KiB
Swift
208 lines
6.9 KiB
Swift
import SwiftUI
|
|
|
|
struct GoalShareCardView: View {
|
|
let name: String
|
|
let progress: Double
|
|
let currentValue: Decimal
|
|
let targetValue: Decimal
|
|
var targetDate: Date? = nil
|
|
var estimatedCompletionDate: Date? = nil
|
|
var privacyMode: Bool = false
|
|
var qrCodeImage: UIImage? = nil
|
|
|
|
private var hasExtraContent: Bool {
|
|
targetDate != nil || estimatedCompletionDate != nil
|
|
}
|
|
|
|
private var cardHeight: CGFloat {
|
|
let base: CGFloat = hasExtraContent ? 340 : 290
|
|
return privacyMode ? base + 10 : base
|
|
}
|
|
|
|
private var displayCurrentValue: String {
|
|
privacyMode ? "***" : currentValue.currencyString
|
|
}
|
|
|
|
private var progressText: String {
|
|
let percent = Int((progress * 100).rounded())
|
|
return "\(percent)%"
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 14) {
|
|
// Header with goal name
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text("Goal Progress")
|
|
.font(.caption.weight(.semibold))
|
|
.foregroundColor(.white.opacity(0.8))
|
|
Text(name)
|
|
.font(.title2.weight(.bold))
|
|
.foregroundColor(.white)
|
|
}
|
|
|
|
GoalProgressBar(
|
|
progress: progress,
|
|
tint: .white.opacity(0.9),
|
|
background: .white.opacity(0.2),
|
|
iconName: "sparkles",
|
|
iconColor: .white
|
|
)
|
|
.frame(height: 10)
|
|
|
|
Text("\(progressText) complete")
|
|
.font(.caption.weight(.semibold))
|
|
.foregroundColor(.white.opacity(0.85))
|
|
|
|
if privacyMode {
|
|
HStack {
|
|
Text("Progress")
|
|
.font(.subheadline.weight(.semibold))
|
|
.foregroundColor(.white.opacity(0.9))
|
|
Spacer()
|
|
Text(progressText)
|
|
.font(.headline.weight(.bold))
|
|
.foregroundColor(.white)
|
|
}
|
|
} else {
|
|
HStack {
|
|
Text(displayCurrentValue)
|
|
.font(.headline)
|
|
.foregroundColor(.white)
|
|
Spacer()
|
|
Text("of \(targetValue.currencyString)")
|
|
.font(.subheadline.weight(.medium))
|
|
.foregroundColor(.white.opacity(0.8))
|
|
}
|
|
}
|
|
|
|
if let targetDate {
|
|
HStack(spacing: 6) {
|
|
Image(systemName: "calendar")
|
|
.font(.caption)
|
|
Text("Target: \(targetDate.mediumDateString)")
|
|
.font(.caption.weight(.medium))
|
|
}
|
|
.foregroundColor(.white.opacity(0.85))
|
|
}
|
|
|
|
if let estimatedCompletionDate {
|
|
HStack(spacing: 6) {
|
|
Image(systemName: "chart.line.uptrend.xyaxis")
|
|
.font(.caption)
|
|
Text("Est. completion: \(estimatedCompletionDate.mediumDateString)")
|
|
.font(.caption.weight(.medium))
|
|
}
|
|
.foregroundColor(.white.opacity(0.85))
|
|
}
|
|
|
|
if privacyMode {
|
|
HStack(spacing: 6) {
|
|
Image(systemName: "eye.slash")
|
|
Text("Privacy mode enabled")
|
|
}
|
|
.font(.caption.weight(.semibold))
|
|
.foregroundColor(.white.opacity(0.7))
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
|
|
// Branding footer with QR code
|
|
VStack(spacing: 10) {
|
|
// Divider line
|
|
Rectangle()
|
|
.fill(Color.white.opacity(0.2))
|
|
.frame(height: 1)
|
|
|
|
HStack(spacing: 12) {
|
|
// App icon and branding
|
|
Image("BrandMark")
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.frame(width: 40, height: 40)
|
|
.clipShape(RoundedRectangle(cornerRadius: 9, style: .continuous))
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text("Powered by")
|
|
.font(.caption2.weight(.medium))
|
|
.foregroundColor(.white.opacity(0.7))
|
|
Text("Portfolio Journal")
|
|
.font(.subheadline.weight(.bold))
|
|
.foregroundColor(.white)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
// QR Code
|
|
if let qrCodeImage {
|
|
VStack(spacing: 4) {
|
|
Image(uiImage: qrCodeImage)
|
|
.interpolation(.none)
|
|
.resizable()
|
|
.frame(width: 50, height: 50)
|
|
.background(Color.white)
|
|
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
|
|
|
|
Text("Scan to download")
|
|
.font(.system(size: 7, weight: .medium))
|
|
.foregroundColor(.white.opacity(0.8))
|
|
}
|
|
} else {
|
|
// Fallback if QR code generation fails
|
|
VStack(spacing: 2) {
|
|
Image(systemName: "qrcode")
|
|
.font(.system(size: 28))
|
|
Text("App Store")
|
|
.font(.caption2.weight(.semibold))
|
|
}
|
|
.foregroundColor(.white.opacity(0.9))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(20)
|
|
.frame(width: 320, height: cardHeight)
|
|
.background(
|
|
LinearGradient(
|
|
colors: [Color.appPrimary, Color.appSecondary],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
)
|
|
.clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 24, style: .continuous)
|
|
.stroke(Color.white.opacity(0.2), lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
GoalShareCardView(
|
|
name: "1M Goal",
|
|
progress: 0.42,
|
|
currentValue: 420_000,
|
|
targetValue: 1_000_000
|
|
)
|
|
}
|
|
|
|
#Preview("With Dates") {
|
|
GoalShareCardView(
|
|
name: "1M Goal",
|
|
progress: 0.42,
|
|
currentValue: 420_000,
|
|
targetValue: 1_000_000,
|
|
targetDate: Date().addingTimeInterval(365 * 24 * 60 * 60),
|
|
estimatedCompletionDate: Date().addingTimeInterval(300 * 24 * 60 * 60)
|
|
)
|
|
}
|
|
|
|
#Preview("Privacy Mode") {
|
|
GoalShareCardView(
|
|
name: "1M Goal",
|
|
progress: 0.42,
|
|
currentValue: 420_000,
|
|
targetValue: 1_000_000,
|
|
privacyMode: true
|
|
)
|
|
}
|