import CoreImage.CIFilterBuiltins import SwiftUI enum Theme { static let background = Color(red: 0.035, green: 0.035, blue: 0.05) static let surface = Color(red: 0.09, green: 0.09, blue: 0.12) static let surfaceAlt = Color(red: 0.13, green: 0.13, blue: 0.17) static let border = Color.white.opacity(0.08) static let accent = Color(red: 1.0, green: 0.56, blue: 0.16) static let accentSoft = Color(red: 1.0, green: 0.56, blue: 0.16).opacity(0.15) static let text = Color.white static let muted = Color.white.opacity(0.55) static let danger = Color(red: 1.0, green: 0.36, blue: 0.36) static let success = Color(red: 0.35, green: 0.85, blue: 0.55) } struct PrimaryButton: View { var title: String var disabled: Bool = false var action: () -> Void var body: some View { Button(action: action) { Text(title) .font(.system(size: 16, weight: .semibold)) .frame(maxWidth: .infinity) .padding(.vertical, 15) .background(disabled ? Theme.surfaceAlt : Theme.accent) .foregroundStyle(disabled ? Theme.muted : Color.black) .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) } .disabled(disabled) } } struct SecondaryButton: View { var title: String var action: () -> Void var body: some View { Button(action: action) { Text(title) .font(.system(size: 15, weight: .medium)) .frame(maxWidth: .infinity) .padding(.vertical, 14) .background(Theme.surface) .foregroundStyle(Theme.text) .overlay(RoundedRectangle(cornerRadius: 14, style: .continuous).stroke(Theme.border)) .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) } } } struct Card: View { @ViewBuilder var content: Content var body: some View { content .padding(18) .frame(maxWidth: .infinity, alignment: .leading) .background(Theme.surface) .overlay(RoundedRectangle(cornerRadius: 20, style: .continuous).stroke(Theme.border)) .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) } } struct FieldLabel: View { var text: String var body: some View { Text(text) .font(.system(size: 12, weight: .medium)) .foregroundStyle(Theme.muted) .padding(.leading, 4) } } struct KoinsTextField: View { var placeholder: String @Binding var text: String var multiline = false var keyboard: UIKeyboardType = .default var autocapitalization: TextInputAutocapitalization = .never var body: some View { Group { if multiline { TextEditor(text: $text) .scrollContentBackground(.hidden) .frame(minHeight: 96) .padding(8) } else { TextField(placeholder, text: $text) .keyboardType(keyboard) .textInputAutocapitalization(autocapitalization) .autocorrectionDisabled() .padding(.horizontal, 14) .padding(.vertical, 13) } } .font(.system(size: 15)) .foregroundStyle(Theme.text) .background(Theme.surface) .overlay(RoundedRectangle(cornerRadius: 14, style: .continuous).stroke(Theme.border)) .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) } } struct MessageBanner: View { var message: String var isError: Bool var body: some View { Text(message) .font(.system(size: 13)) .foregroundStyle(isError ? Theme.danger : Theme.success) .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background((isError ? Theme.danger : Theme.success).opacity(0.12)) .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) } } struct ChainPicker: View { @Binding var selection: Chain var body: some View { HStack(spacing: 6) { ForEach(Chain.allCases, id: \.self) { chain in Button { selection = chain } label: { Text(chain.title) .font(.system(size: 13, weight: .semibold)) .frame(maxWidth: .infinity) .padding(.vertical, 10) .background(selection == chain ? Theme.accent : Color.clear) .foregroundStyle(selection == chain ? Color.black : Theme.muted) .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) } } } .padding(4) .background(Theme.surface) .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) } } struct QRCodeView: View { var text: String var body: some View { if let image = QRCodeView.makeImage(from: text) { Image(uiImage: image) .interpolation(.none) .resizable() .scaledToFit() .padding(12) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) } else { RoundedRectangle(cornerRadius: 16, style: .continuous) .fill(Theme.surfaceAlt) .aspectRatio(1, contentMode: .fit) } } private static func makeImage(from text: String) -> UIImage? { QRCodeImage.make(text) } } struct TransactionRow: View { var record: TransactionRecord var symbol: String private var subtitle: String { if let timestamp = record.timestamp { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .short return record.confirmed ? formatter.string(from: timestamp) : "Pending confirmation" } // Monero transfers carry no timestamp; the block height is the honest label. if let height = record.height, height > 0 { return record.confirmed ? "Block \(height)" : "In the transaction pool" } return record.confirmed ? "Confirmed" : "Pending" } var body: some View { HStack(spacing: 12) { ZStack { Circle() .fill(record.direction == .incoming ? Theme.success.opacity(0.15) : Theme.accentSoft) .frame(width: 38, height: 38) Image(systemName: record.direction == .incoming ? "arrow.down.left" : "arrow.up.right") .font(.system(size: 15, weight: .bold)) .foregroundStyle(record.direction == .incoming ? Theme.success : Theme.accent) } VStack(alignment: .leading, spacing: 3) { Text(record.direction == .incoming ? "Received" : "Sent") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(Theme.text) Text(subtitle) .font(.system(size: 11)) .foregroundStyle(Theme.muted) } Spacer() VStack(alignment: .trailing, spacing: 3) { Text("\(record.direction == .incoming ? "+" : "-")\(record.amount) \(symbol)") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(record.direction == .incoming ? Theme.success : Theme.text) if let fee = record.fee, !fee.isEmpty, record.direction == .outgoing { Text("fee \(fee)") .font(.system(size: 11)) .foregroundStyle(Theme.muted) } } } .padding(.vertical, 6) } } struct ScreenHeader: View { var title: String var subtitle: String? var onBack: (() -> Void)? var body: some View { HStack(alignment: .center, spacing: 12) { if let onBack { Button(action: onBack) { Image(systemName: "chevron.left") .font(.system(size: 16, weight: .semibold)) .foregroundStyle(Theme.text) .frame(width: 34, height: 34) .background(Theme.surface) .clipShape(Circle()) } } VStack(alignment: .leading, spacing: 2) { Text(title) .font(.system(size: 22, weight: .bold)) .foregroundStyle(Theme.text) if let subtitle { Text(subtitle) .font(.system(size: 12)) .foregroundStyle(Theme.muted) } } Spacer() } } }