mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
Replaces every stringly-typed localization key in the Apple app with compile-time-checked accessors generated from localization/strings.json. A mistyped key is now a build error instead of a silent fallback to the raw key at runtime. The runtime path is unchanged: plain keys are String.LocalizationValue constants resolved with String(localized:) and Apple's String Catalog still does the lookup; keys with arguments become typed, named functions applying args through String(format:). Generator: new renderSwiftAccessors emits apple/VniDrop/Generated/L10n.swift, wired into generate. Renamed generic arg1/arg2 tokens on four keys to semantic names (receiver, transferName, deviceId) and updated their context notes; positional output is unchanged so .xcstrings (bar the 4 comments) and the Android XML regenerate identical. Migration: every key-carrying value flipped to String.LocalizationValue end to end, resolved only at the leaf. Zero key literals and zero LocalizedStringKey remain in app or test code. macOS build passes; iOS test run pending.
97 lines
2.2 KiB
Swift
97 lines
2.2 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - StatusPill
|
|
|
|
enum PillTone { case neutral, success, warning, destructive, brand }
|
|
|
|
struct StatusPill: View {
|
|
let label: String
|
|
var tone: PillTone = .neutral
|
|
|
|
private var color: Color {
|
|
switch tone {
|
|
case .neutral: return .secondary
|
|
case .success, .brand: return VniDropColors.brandPurple
|
|
case .warning: return .orange
|
|
case .destructive: return .red
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(spacing: 5) {
|
|
Circle().fill(color).frame(width: 6, height: 6)
|
|
Text(label).font(.caption).fontWeight(.medium).foregroundStyle(color).lineLimit(1)
|
|
}
|
|
.padding(.horizontal, 9).padding(.vertical, 4)
|
|
.background(color.opacity(0.14), in: Capsule())
|
|
}
|
|
}
|
|
|
|
// MARK: - ProgressRow
|
|
|
|
struct ProgressRow: View {
|
|
let labelKey: String.LocalizationValue
|
|
let progress: Double?
|
|
var detail: String? = nil
|
|
/// Pre-resolved label; when set it overrides `labelKey`.
|
|
var labelText: String? = nil
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
HStack {
|
|
label.font(.subheadline).lineLimit(1)
|
|
Spacer()
|
|
if let progress {
|
|
Text("\(Int(progress * 100))%").font(.caption).foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
if let detail {
|
|
Text(detail).font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
|
}
|
|
if let progress {
|
|
ProgressView(value: progress)
|
|
} else {
|
|
ProgressView().progressViewStyle(.linear)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var label: some View {
|
|
if let labelText {
|
|
Text(labelText)
|
|
} else {
|
|
Text(String(localized: labelKey))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Field
|
|
|
|
/// Labeled text field using native styling. Renders cleanly both inside a `Form`
|
|
/// row and standalone (e.g. inside a sheet).
|
|
struct Field: View {
|
|
let label: String
|
|
@Binding var value: String
|
|
var minLines: Int = 1
|
|
var enabled: Bool = true
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(label).font(.subheadline).foregroundStyle(.secondary)
|
|
Group {
|
|
if minLines > 1 {
|
|
TextField(label, text: $value, axis: .vertical)
|
|
.lineLimit(minLines, reservesSpace: true)
|
|
} else {
|
|
TextField(label, text: $value)
|
|
}
|
|
}
|
|
.textFieldStyle(.roundedBorder)
|
|
.disabled(!enabled)
|
|
}
|
|
}
|
|
}
|
|
|