8084bc6a82
Rediseño de las cards de compartir con el sistema visual de las screenshots ASO: fondo navy con glows y grid, sparkline de evolución con gradiente azul→cian y punto final, chip de delta, footer con marca + QR. Aplica a Portfolio Snapshot, Monthly Check-in y Monthly Summary. Potencia referral: sheet de opciones previo con preview en vivo (PortfolioShareOptionsView) — toggle «Solo porcentajes» (sin importes absolutos, la fricción #1 para compartir) y formato Post/Story 9:16. QR y link con ?ct=snapshot_share para atribución en App Analytics. Analytics: share type portfolio_value_percent. L10n ×7. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L38583J7AYWCVPevkivscj
146 lines
5.4 KiB
Swift
146 lines
5.4 KiB
Swift
import SwiftUI
|
|
|
|
/// A polished, branded card summarising the month — designed to be rendered to
|
|
/// an image and shared (screenshot-worthy, marketing-facing). Pulls only from
|
|
/// data the Dashboard already computes.
|
|
struct MonthlySummaryShareView: View {
|
|
let monthLabel: String
|
|
let totalValue: String
|
|
let changeAmount: String
|
|
let changePercentage: String
|
|
let isPositive: Bool
|
|
let streak: Int
|
|
let mood: MonthlyCheckInMood?
|
|
let rating: Int?
|
|
let appName: String
|
|
var qrCodeImage: UIImage? = nil
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
// Header
|
|
HStack {
|
|
Text(monthLabel)
|
|
.font(.caption.weight(.semibold))
|
|
.foregroundColor(.white.opacity(0.85))
|
|
.textCase(.uppercase)
|
|
Spacer()
|
|
if streak >= 2 {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "flame.fill")
|
|
.font(.caption2)
|
|
Text(String(format: String(localized: "share_summary_streak_badge"), streak))
|
|
.font(.caption2.weight(.bold))
|
|
}
|
|
.foregroundColor(.white)
|
|
.padding(.horizontal, 10)
|
|
.padding(.vertical, 5)
|
|
.background(Color.white.opacity(0.18))
|
|
.clipShape(Capsule())
|
|
}
|
|
}
|
|
|
|
Text("Monthly Summary")
|
|
.font(.title.weight(.bold))
|
|
.foregroundColor(.white)
|
|
.padding(.top, 6)
|
|
|
|
// Hero value
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text("Portfolio Value")
|
|
.font(.subheadline.weight(.semibold))
|
|
.foregroundColor(.white.opacity(0.85))
|
|
Text(totalValue)
|
|
.font(.system(size: 40, weight: .bold, design: .rounded))
|
|
.foregroundColor(.white)
|
|
.minimumScaleFactor(0.6)
|
|
.lineLimit(1)
|
|
}
|
|
.padding(.top, 22)
|
|
|
|
// Change since last check-in
|
|
HStack(spacing: 8) {
|
|
Image(systemName: isPositive ? "arrow.up.right" : "arrow.down.right")
|
|
.font(.subheadline.weight(.bold))
|
|
Text("\(changeAmount) (\(changePercentage))")
|
|
.font(.headline.weight(.bold))
|
|
Spacer()
|
|
Text("since last check-in")
|
|
.font(.caption)
|
|
.foregroundColor(.white.opacity(0.75))
|
|
}
|
|
.foregroundColor(.white)
|
|
.padding(.horizontal, 14)
|
|
.padding(.vertical, 10)
|
|
.background(Color.white.opacity(0.15))
|
|
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|
.padding(.top, 18)
|
|
|
|
// Mood / rating
|
|
if mood != nil || rating != nil {
|
|
HStack(spacing: 14) {
|
|
if let mood {
|
|
HStack(spacing: 6) {
|
|
Image(systemName: mood.iconName)
|
|
.font(.subheadline)
|
|
Text(mood.title)
|
|
.font(.subheadline.weight(.semibold))
|
|
}
|
|
.foregroundColor(.white)
|
|
}
|
|
if let rating {
|
|
HStack(spacing: 2) {
|
|
ForEach(1...5, id: \.self) { i in
|
|
Image(systemName: i <= rating ? "star.fill" : "star")
|
|
.font(.caption)
|
|
.foregroundColor(.white.opacity(i <= rating ? 1 : 0.4))
|
|
}
|
|
}
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding(.top, 16)
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
|
|
// Footer / branding
|
|
HStack(spacing: 12) {
|
|
Image("BrandMark")
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.frame(width: 40, height: 40)
|
|
.clipShape(RoundedRectangle(cornerRadius: 9, style: .continuous))
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text("Tracked with")
|
|
.font(.caption2.weight(.medium))
|
|
.foregroundColor(.white.opacity(0.7))
|
|
Text(appName)
|
|
.font(.subheadline.weight(.bold))
|
|
.foregroundColor(.white)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if let qrCodeImage {
|
|
Image(uiImage: qrCodeImage)
|
|
.interpolation(.none)
|
|
.resizable()
|
|
.frame(width: 52, height: 52)
|
|
.background(Color.white)
|
|
.clipShape(RoundedRectangle(cornerRadius: 7, style: .continuous))
|
|
}
|
|
}
|
|
.padding(.top, 20)
|
|
}
|
|
.padding(24)
|
|
.frame(width: 340, height: 440)
|
|
.background(ShareCardBackground())
|
|
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 28, style: .continuous)
|
|
.stroke(Color.white.opacity(0.12), lineWidth: 1)
|
|
)
|
|
}
|
|
}
|