feat: add iOS support and harden receipt scanning

This commit is contained in:
Timo
2026-08-20 23:48:26 +02:00
parent f5e06c32b0
commit cf1b799e1b
107 changed files with 11755 additions and 122 deletions

View File

@@ -0,0 +1,79 @@
import SwiftUI
/// Input fields per `DESIGN (1).md` Components Input Fields: "1px solid
/// #E2E8F0 bottom-border only... Use `label-caps` for field labels placed
/// strictly above the input." Border thickens to 2px while focused the
/// same "shift, don't lift" interaction rule as `ZenithButtonStyle`.
///
/// Use this instead of a bare `TextField` + `.textFieldStyle(.roundedBorder)`
/// everywhere in the app; `.roundedBorder` fights this design system
/// directly (it's rounded and iOS-chrome-grey, the opposite of "sharp,
/// monochrome, architectural").
struct ZenithTextField: View {
let label: String
@Binding var text: String
var placeholder: String = ""
var isSecure = false
var keyboardType: UIKeyboardType = .default
var textContentType: UITextContentType?
var autocapitalization: TextInputAutocapitalization = .sentences
var autocorrectionDisabled = false
@FocusState private var isFocused: Bool
var body: some View {
VStack(alignment: .leading, spacing: ZenithSpacing.unit) {
Text(label).zenithLabelCapsStyle()
Group {
if isSecure {
SecureField(placeholder, text: $text)
} else {
TextField(placeholder, text: $text)
}
}
.font(.zenithBodyMd)
.foregroundStyle(.zenithText)
.keyboardType(keyboardType)
.textContentType(textContentType)
.textInputAutocapitalization(autocapitalization)
.autocorrectionDisabled(autocorrectionDisabled)
.focused($isFocused)
.padding(.vertical, ZenithSpacing.unit * 2)
.overlay(alignment: .bottom) {
Rectangle()
.fill(isFocused ? Color.zenithBlack : Color.zenithBorder)
.frame(height: isFocused ? 2 : 1)
}
}
}
}
/// A numeric variant bound to an optional `Double`, for amount fields free
/// text with comma-or-dot decimal entry (matches how receipts are actually
/// typed in German usage) rather than a strict `TextField(value:format:)`
/// that rejects a comma outright.
struct ZenithAmountField: View {
let label: String
@Binding var value: Double?
var placeholder: String = "0,00"
private var textProxy: Binding<String> {
Binding(
get: { value.map { String(format: "%.2f", $0) } ?? "" },
set: { newValue in
let normalized = newValue.replacingOccurrences(of: ",", with: ".")
value = Double(normalized)
}
)
}
var body: some View {
ZenithTextField(
label: label,
text: textProxy,
placeholder: placeholder,
keyboardType: .decimalPad
)
}
}