import SwiftUI struct DashboardView: View { @EnvironmentObject private var model: AppModel @Binding var route: Route @State private var showSeed = false @State private var revealedSeed: String? @State private var seedError: String? var body: some View { VStack(alignment: .leading, spacing: 18) { ScreenHeader(title: model.wallet?.name ?? "Wallet", subtitle: model.chain.title, onBack: { route = .home }) Card { VStack(alignment: .leading, spacing: 10) { Text("Balance") .font(.system(size: 11, weight: .medium)) .foregroundStyle(Theme.muted) Text("\(model.balance.total) \(model.symbol)") .font(.system(size: 32, weight: .bold, design: .rounded)) .foregroundStyle(Theme.text) if model.chain == .xmr, model.balance.unlocked != model.balance.total { Text("\(model.balance.unlocked) \(model.symbol) spendable now") .font(.system(size: 11)) .foregroundStyle(Theme.muted) } AddressRow(address: model.displayAddress) } } if model.chain == .xmr { Card { VStack(alignment: .leading, spacing: 10) { HStack { Text("Sync") .font(.system(size: 13, weight: .semibold)) .foregroundStyle(Theme.text) Spacer() if let status = model.syncStatus { SyncBadge(status: status) } } if let status = model.syncStatus { SyncProgress(status: status, eta: model.syncEta) } else { Text("Sync status is not available yet.") .font(.system(size: 11)) .foregroundStyle(Theme.muted) } } } } HStack(spacing: 10) { PrimaryButton(title: "Send") { route = .send } SecondaryButton(title: "Receive") { route = .receive } } if !model.transactions.isEmpty { Card { VStack(alignment: .leading, spacing: 10) { Text("Activity") .font(.system(size: 13, weight: .semibold)) .foregroundStyle(Theme.muted) ForEach(model.transactions) { record in TransactionRow(record: record, symbol: model.symbol) if record.id != model.transactions.last?.id { Divider().overlay(Theme.border) } } } } } if let message = model.errorMessage { MessageBanner(message: message, isError: true) } if let message = model.statusMessage { MessageBanner(message: message, isError: false) } SecondaryButton(title: "Reveal recovery phrase") { Task { seedError = nil do { revealedSeed = try await model.revealSeed() showSeed = true } catch { seedError = error.localizedDescription } } } if let seedError { MessageBanner(message: seedError, isError: true) } } .sheet(isPresented: $showSeed) { if let revealedSeed { SeedRevealView(seed: revealedSeed, symbol: model.symbol) { showSeed = false } } } } } struct SendView: View { @EnvironmentObject private var model: AppModel @Binding var route: Route @State private var destination = "" @State private var amount = "" @State private var tag = "" @State private var priority: TransactionPriority = .defaultPriority @State private var localError: String? var body: some View { VStack(alignment: .leading, spacing: 18) { ScreenHeader(title: "Send \(model.symbol)", subtitle: "Balance \(model.balance.total) \(model.symbol)", onBack: { route = .home }) VStack(alignment: .leading, spacing: 6) { FieldLabel(text: model.chain == .xrp ? "Destination address" : "Recipient address") KoinsTextField(placeholder: model.chain == .xrp ? "r…" : "4…", text: $destination) } VStack(alignment: .leading, spacing: 6) { FieldLabel(text: "Amount (\(model.symbol))") KoinsTextField(placeholder: "0.0", text: $amount, keyboard: .decimalPad) } if model.chain == .xrp { VStack(alignment: .leading, spacing: 6) { FieldLabel(text: "Destination tag (optional)") KoinsTextField(placeholder: "e.g. 12345", text: $tag, keyboard: .numberPad) } } if model.chain == .xmr { VStack(alignment: .leading, spacing: 8) { FieldLabel(text: "Priority") Picker("Priority", selection: $priority) { ForEach([TransactionPriority.low, .medium, .high, .defaultPriority], id: \.rawValue) { item in Text(item.label).tag(item) } } .pickerStyle(.segmented) } } if let message = localError ?? model.errorMessage { MessageBanner(message: message, isError: true) } if let message = model.statusMessage { MessageBanner(message: message, isError: false) } PrimaryButton(title: model.isBusy ? "Sending…" : "Send", disabled: model.isBusy) { Task { localError = nil model.clearMessages() do { try await model.send(to: destination, amount: amount, tag: tag, priority: priority) amount = "" tag = "" route = .dashboard } catch { localError = error.localizedDescription } } } Spacer(minLength: 0) } } } struct ReceiveView: View { @EnvironmentObject private var model: AppModel @Binding var route: Route private var address: String { model.displayAddress } var body: some View { VStack(alignment: .leading, spacing: 18) { ScreenHeader(title: "Receive \(model.symbol)", subtitle: "Share this address to get paid", onBack: { route = .home }) Card { VStack(spacing: 16) { QRCodeView(text: address) .frame(maxWidth: 240) Text(address) .font(.system(size: 12, design: .monospaced)) .foregroundStyle(Theme.muted) .multilineTextAlignment(.center) .lineLimit(3) .truncationMode(.middle) } .frame(maxWidth: .infinity) } SecondaryButton(title: "Copy address") { UIPasteboard.general.string = address } if model.chain == .xrp { Card { Text("XRP accounts must hold at least 1 XRP as a reserve to be active. Only send XRP on the XRP Ledger to this address.") .font(.system(size: 12)) .foregroundStyle(Theme.muted) } } else { Card { Text("Send only Monero (XMR) to this address. Transfers need confirmations before the balance becomes spendable.") .font(.system(size: 12)) .foregroundStyle(Theme.muted) } } Spacer(minLength: 0) } } }