Share cards compartibles: brand header grande, fecha, Dark/Light (build 83) #35

- Header de marca arriba, grande (logo BrandMark 48px + nombre title2 rounded),
  nunca se corta en Story (9:16 real 360x640, Post 4:5 360x450).
- Héroe = retorno % (privacy/branding-friendly), valor como apoyo.
- Fecha del último check-in bajo el nombre (share_as_of) para dar contexto a
  quien no conoce la app; DashboardViewModel.shareAsOfDate.
- Tema Dark + Light (ShareCardTheme) con selector en el sheet + preview vivo.
- Sparkline real (viewModel.evolutionData), ya se pasaba.
- CTA de descarga + QR con ct=snapshot_share (atribución App Analytics).
- L10n x7 (as_of, theme_dark/light, cta, hero, metrics).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L38583J7AYWCVPevkivscj
This commit is contained in:
alexandrev-tibco
2026-08-07 09:45:10 +02:00
parent 8084bc6a82
commit 3b1f62cc26
12 changed files with 326 additions and 113 deletions
+23 -13
View File
@@ -22,15 +22,18 @@ class ShareService {
}
struct PortfolioCardStrings {
/// The big number (an all-time/return %) the hero of the card.
let heroPercent: String?
/// Supporting absolute value (empty in percent-only mode).
let totalValue: String
let changeText: String
let yearChange: String?
let sinceInceptionChange: String?
}
/// Privacy transform: with `percentOnly` the card leads with the all-time
/// return and every row drops absolute amounts. Nobody shares their net
/// worth in euros; everybody shares +42%.
/// The all-time return is always the hero (branding + shareable). With
/// `percentOnly` every absolute amount is dropped nobody posts their net
/// worth in euros; everybody posts +42%.
static func portfolioCardStrings(
totalValue: String,
changeText: String,
@@ -38,20 +41,22 @@ class ShareService {
sinceInceptionChange: String?,
percentOnly: Bool
) -> PortfolioCardStrings {
guard percentOnly else {
let hero = percentPart(sinceInceptionChange) ?? percentPart(changeText)
if percentOnly {
return PortfolioCardStrings(
totalValue: totalValue, changeText: changeText,
yearChange: yearChange, sinceInceptionChange: sinceInceptionChange
heroPercent: hero ?? totalValue,
totalValue: "",
changeText: percentPart(changeText) ?? changeText,
yearChange: percentPart(yearChange),
sinceInceptionChange: percentPart(sinceInceptionChange)
)
}
let heroPercent = percentPart(sinceInceptionChange)
?? percentPart(changeText)
?? totalValue
return PortfolioCardStrings(
totalValue: heroPercent,
changeText: percentPart(changeText) ?? changeText,
yearChange: percentPart(yearChange),
sinceInceptionChange: percentPart(sinceInceptionChange)
heroPercent: hero,
totalValue: totalValue,
changeText: changeText,
yearChange: yearChange,
sinceInceptionChange: sinceInceptionChange
)
}
@@ -168,6 +173,8 @@ class ShareService {
sinceInceptionChange: String?,
sparkline: [Double] = [],
isPositive: Bool = true,
asOf: String? = nil,
theme: ShareCardTheme = .dark,
percentOnly: Bool = false,
storyFormat: Bool = false
) {
@@ -205,6 +212,9 @@ class ShareService {
qrCodeImage: GoalShareService.generateQRCode(for: Self.snapshotShareURL, size: 200),
sparkline: sparkline,
isPositive: isPositive,
heroPercent: display.heroPercent,
asOf: asOf,
theme: theme,
storyFormat: storyFormat
)
}