Release 1.4.0 (build 31): retención, quick update, streak, what's new

- 1A: Notificación mensual de resumen de portfolio (día 5 de cada mes)
- 1B: Badge de racha mensual en Dashboard (streak counter)
- 1C: Empty states mejorados en Goals y Journal con CTAs claros
- 2A: Quick Update sheet — actualiza todas las fuentes desde una pantalla
- 2B: Notificación batch update redirige al Quick Update sheet
- 2C: Widget deep link portfoliojournal://quickupdate abre Quick Update
- 3A: App Store version check banner en Settings
- 3C: What's New sheet en primer launch de versión nueva
- Localización completa en 7 idiomas para todas las nuevas strings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-05-21 23:09:36 +02:00
parent 773da6800b
commit b48a47ce10
41 changed files with 2659 additions and 483 deletions
@@ -0,0 +1,36 @@
import Foundation
import Combine
@MainActor
class AppUpdateService: ObservableObject {
static let shared = AppUpdateService()
@Published var updateAvailable = false
@Published var latestVersion: String?
private init() {}
func checkForUpdate() {
guard let bundleId = Bundle.main.bundleIdentifier else { return }
let urlString = "https://itunes.apple.com/lookup?bundleId=\(bundleId)"
guard let url = URL(string: urlString) else { return }
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let results = json["results"] as? [[String: Any]],
let first = results.first,
let appStoreVersion = first["version"] as? String,
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
else { return }
if appStoreVersion.compare(currentVersion, options: .numeric) == .orderedDescending {
self.updateAvailable = true
self.latestVersion = appStoreVersion
}
} catch {
// silently fail
}
}
}
}