159 lines
4.9 KiB
Swift
159 lines
4.9 KiB
Swift
import SwiftUI
|
|
|
|
struct AccountsView: View {
|
|
@EnvironmentObject private var model: AppModel
|
|
@Binding var route: Route
|
|
|
|
@State private var error: String?
|
|
@State private var busy = false
|
|
@State private var qrAddress: AddressQR?
|
|
@State private var showAccountPrompt = false
|
|
@State private var showSubaddressPrompt = false
|
|
@State private var pendingLabel = ""
|
|
@State private var subaddressAccount = 0
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 18) {
|
|
ScreenHeader(title: "Accounts & addresses",
|
|
subtitle: "Each address gets its own QR code",
|
|
onBack: { route = .home })
|
|
|
|
PrimaryButton(title: busy ? "Working…" : "New account", disabled: busy) {
|
|
showAccountPrompt = true
|
|
}
|
|
|
|
if let error {
|
|
MessageBanner(message: error, isError: true)
|
|
}
|
|
|
|
ForEach(model.moneroAccounts) { account in
|
|
accountCard(account)
|
|
}
|
|
|
|
if model.moneroAccounts.isEmpty {
|
|
Text("No accounts yet. Accounts and addresses appear after the wallet opens.")
|
|
.font(.system(size: 12))
|
|
.foregroundStyle(Theme.muted)
|
|
}
|
|
}
|
|
.task(id: model.moneroReady) { await model.loadAccounts() }
|
|
.bottomDrawer(isPresented: Binding(
|
|
get: { qrAddress != nil },
|
|
set: { if !$0 { qrAddress = nil } }
|
|
)) {
|
|
if let item = qrAddress {
|
|
AddressQRSheet(title: item.title, address: item.address) { qrAddress = nil }
|
|
}
|
|
}
|
|
.alert("Account name", isPresented: $showAccountPrompt) {
|
|
TextField("e.g. Savings", text: $pendingLabel)
|
|
Button("Create") { createAccount() }
|
|
Button("Cancel", role: .cancel) { pendingLabel = "" }
|
|
}
|
|
.alert("Address label", isPresented: $showSubaddressPrompt) {
|
|
TextField("e.g. From Alice", text: $pendingLabel)
|
|
Button("Create") { createSubaddress() }
|
|
Button("Cancel", role: .cancel) { pendingLabel = "" }
|
|
}
|
|
}
|
|
|
|
private func accountCard(_ account: MoneroAccount) -> some View {
|
|
Card {
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: 3) {
|
|
Text(account.title)
|
|
.font(.system(size: 15, weight: .semibold))
|
|
.foregroundStyle(Theme.text)
|
|
Text("\(account.balance) XMR")
|
|
.font(.system(size: 12))
|
|
.foregroundStyle(Theme.muted)
|
|
}
|
|
Spacer()
|
|
}
|
|
|
|
addressRow(title: "Primary address",
|
|
address: account.address,
|
|
isUsed: true)
|
|
|
|
if !account.subaddresses.isEmpty {
|
|
Divider().overlay(Theme.border)
|
|
ForEach(account.subaddresses.filter { $0.index != 0 }) { subaddress in
|
|
addressRow(title: subaddress.label.isEmpty ? "Address \(subaddress.index)" : subaddress.label,
|
|
address: subaddress.address,
|
|
isUsed: subaddress.isUsed,
|
|
balance: subaddress.balance)
|
|
}
|
|
}
|
|
|
|
SecondaryButton(title: "New address in this account") {
|
|
subaddressAccount = account.index
|
|
pendingLabel = ""
|
|
showSubaddressPrompt = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addressRow(title: String, address: String, isUsed: Bool, balance: String? = nil) -> some View {
|
|
Button {
|
|
qrAddress = AddressQR(title: title, address: address)
|
|
} label: {
|
|
HStack(spacing: 8) {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(title)
|
|
.font(.system(size: 12, weight: .medium))
|
|
.foregroundStyle(Theme.muted)
|
|
Text(address.isEmpty ? "—" : address)
|
|
.font(.system(size: 11, design: .monospaced))
|
|
.foregroundStyle(Theme.text)
|
|
.lineLimit(1)
|
|
.truncationMode(.middle)
|
|
if let balance {
|
|
Text("\(balance) XMR\(isUsed ? "" : " · unused")")
|
|
.font(.system(size: 11))
|
|
.foregroundStyle(Theme.muted)
|
|
}
|
|
}
|
|
Spacer(minLength: 6)
|
|
Image(systemName: "qrcode")
|
|
.font(.system(size: 14, weight: .semibold))
|
|
.foregroundStyle(Theme.accent)
|
|
.frame(width: 32, height: 32)
|
|
.background(Theme.surfaceAlt)
|
|
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
|
|
}
|
|
.contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
.disabled(address.isEmpty)
|
|
}
|
|
|
|
private func createAccount() {
|
|
busy = true
|
|
error = nil
|
|
Task {
|
|
defer { busy = false }
|
|
do {
|
|
try await model.addAccount(label: pendingLabel)
|
|
pendingLabel = ""
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
|
|
private func createSubaddress() {
|
|
busy = true
|
|
error = nil
|
|
Task {
|
|
defer { busy = false }
|
|
do {
|
|
try await model.addSubaddress(accountIndex: subaddressAccount, label: pendingLabel)
|
|
pendingLabel = ""
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
} |