Release 1.2.1: iCloud sync improvements + ASO multilingual metadata

iCloud sync:
- Force viewContext.refreshAllObjects() on remote change notifications so
  data from other devices is picked up immediately without app restart
- Call refreshFromCloudKit() on foreground to merge any changes made while
  the device was inactive
- Wait up to 10s for initial CloudKit sync on launch before showing onboarding
  (shows "Checking iCloud..." during the wait)
- New OnboardingICloudCheckView: shown on fresh installs with iCloud available,
  lets user restore from iCloud before starting onboarding from scratch

Localization:
- Added de, fr, it, ja, pt-BR lproj folders
- New iCloud onboarding strings in en + es-ES (+ button literals)

ASO metadata (fastlane):
- Updated en-US: new subtitle, keywords, description (fixed "no cloud sync"
  claim), promotional text, release notes
- Added full metadata for es-ES, de-DE, fr-FR, it, ja, pt-BR (63 files total)
- All keyword fields validated ≤100 Unicode chars

Infrastructure:
- Gemfile + Gemfile.lock for fastlane
- Scripts/archive_and_upload_appstore.sh for CI/CD

Version: 1.2.1 (build 7)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-03-25 11:54:35 +01:00
parent 7cb5f92cf4
commit 10f6d0ca20
108 changed files with 4069 additions and 238 deletions
@@ -9,6 +9,7 @@ struct EvolutionChartCard: View {
@State private var selectedDataPoint: (date: Date, value: Decimal)?
@State private var chartMode: ChartMode = .total
@State private var showGoalLines = true
@State private var chartWidth: CGFloat = 300
enum ChartMode: String, CaseIterable, Identifiable {
case total = "Total"
@@ -17,6 +18,30 @@ struct EvolutionChartCard: View {
var id: String { rawValue }
}
private static let compactXAxisDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .autoupdatingCurrent
formatter.setLocalizedDateFormatFromTemplate("MMM yy")
return formatter
}()
/// Calculates the optimal month stride so labels never overlap,
/// using the actual rendered width of the chart instead of just data count.
private func xAxisMonthStride(for width: CGFloat) -> Int {
// ~50pt for Y-axis, ~44pt per "Jan 24" label
let usableWidth = max(width - 50, 80)
let maxLabels = max(2, Int(usableWidth / 44))
let rawStride = max(1, Int(ceil(Double(data.count) / Double(maxLabels))))
switch rawStride {
case ...1: return 1
case ...2: return 2
case ...3: return 3
case ...4: return 4
case ...6: return 6
default: return 12
}
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
headerView
@@ -84,8 +109,17 @@ struct EvolutionChartCard: View {
}
.chartForegroundStyleScale(domain: chartCategoryNames, range: chartCategoryColors)
.chartXAxis {
AxisMarks(values: .stride(by: .month, count: 3)) { value in
AxisValueLabel(format: .dateTime.month(.abbreviated).year())
AxisMarks(values: .stride(by: .month, count: xAxisMonthStride(for: chartWidth))) { value in
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.8, dash: [3, 3]))
.foregroundStyle(Color.secondary.opacity(0.2))
AxisTick(stroke: StrokeStyle(lineWidth: 0.8))
.foregroundStyle(Color.secondary.opacity(0.28))
AxisValueLabel {
if let date = value.as(Date.self) {
Text(date, formatter: Self.compactXAxisDateFormatter)
.font(.caption2)
}
}
}
}
.chartYAxis {
@@ -124,6 +158,13 @@ struct EvolutionChartCard: View {
}
}
.frame(height: 200)
.background(
GeometryReader { geo in
Color.clear
.onAppear { chartWidth = geo.size.width }
.onChange(of: geo.size.width) { _, w in chartWidth = w }
}
)
// Performance: Use GPU rendering for smoother scrolling
.drawingGroup()
}