diff --git a/apps/ios/Koins/Views/Components.swift b/apps/ios/Koins/Views/Components.swift index 7ab2cc7..f2fc25e 100644 --- a/apps/ios/Koins/Views/Components.swift +++ b/apps/ios/Koins/Views/Components.swift @@ -258,4 +258,53 @@ struct ScreenHeader: View { Spacer() } } -} \ No newline at end of file +} +/// Monero's chain sync as its own section, rather than buried in the wallet card. +struct SyncCard: View { + @EnvironmentObject private var model: AppModel + + var body: some View { + Card { + VStack(alignment: .leading, spacing: 12) { + HStack { + Text("Sync") + .font(.system(size: 13, weight: .semibold)) + .foregroundStyle(Theme.muted) + 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) + } + + SecondaryButton(title: model.syncStatus?.syncing == true ? "Syncing…" : "Sync now") { + Task { await model.startMoneroSync() } + } + } + } + } +} + +/// A small circular icon button for utility actions (refresh, delete, …). +struct IconButton: View { + var systemName: String + var destructive = false + var action: () -> Void + + var body: some View { + Button(action: action) { + Image(systemName: systemName) + .font(.system(size: 14, weight: .semibold)) + .foregroundStyle(destructive ? Theme.danger : Theme.muted) + .frame(width: 42, height: 42) + .background(Theme.surface) + .overlay(Circle().stroke(Theme.border)) + .clipShape(Circle()) + } + } +} diff --git a/apps/ios/Koins/Views/RootView.swift b/apps/ios/Koins/Views/RootView.swift index 3ae02be..4bee2cf 100644 --- a/apps/ios/Koins/Views/RootView.swift +++ b/apps/ios/Koins/Views/RootView.swift @@ -91,7 +91,6 @@ struct RootView: View { struct HomeView: View { @EnvironmentObject private var model: AppModel @Binding var route: Route - @State private var showDeleteConfirm = false @State private var selectedTransaction: TransactionRecord? @State private var showSettings = false @State private var qrAddress: AddressQR? @@ -137,19 +136,6 @@ struct HomeView: View { SettingsView() .environmentObject(model) } - .confirmationDialog("Delete this wallet?", - isPresented: $showDeleteConfirm, - titleVisibility: .visible) { - Button("Delete wallet", role: .destructive) { - Task { - await model.deleteWallet() - route = .home - } - } - Button("Cancel", role: .cancel) {} - } message: { - Text("The keys stored on this device are removed. Make sure your recovery seed is backed up.") - } } private var header: some View { @@ -189,9 +175,6 @@ struct HomeView: View { .font(.system(size: 16, weight: .semibold)) .foregroundStyle(Theme.text) Spacer() - if model.chain == .xmr, let status = model.syncStatus { - SyncBadge(status: status) - } } if model.chain == .xmr, !model.moneroAccounts.isEmpty { @@ -223,25 +206,25 @@ struct HomeView: View { AddressRow(address: model.displayAddress) } - if model.chain == .xmr, let status = model.syncStatus { - SyncProgress(status: status, eta: model.syncEta) - } } } + if model.chain == .xmr { + SyncCard() + } + HStack(spacing: 10) { PrimaryButton(title: "Send") { route = .send } SecondaryButton(title: "Receive") { route = .receive } SecondaryButton(title: "Swap") { route = .swap } } - if model.chain == .xmr { - HStack(spacing: 10) { - SecondaryButton(title: model.syncStatus?.syncing == true ? "Syncing…" : "Sync now") { - Task { await model.startMoneroSync() } - } - SecondaryButton(title: "Addresses") { route = .accounts } + HStack(spacing: 12) { + IconButton(systemName: "arrow.clockwise") { Task { await model.refresh(force: true) } } + if model.chain == .xmr { + IconButton(systemName: "person.2.fill") { route = .accounts } } + Spacer() } if model.chain == .xrp, model.moneroReady, let status = model.syncStatus, status.syncing { @@ -264,10 +247,6 @@ struct HomeView: View { } } - HStack(spacing: 10) { - SecondaryButton(title: "Refresh") { Task { await model.refresh(force: true) } } - SecondaryButton(title: "Delete") { showDeleteConfirm = true } - } if !model.transactions.isEmpty { Card { diff --git a/apps/ios/Koins/Views/SettingsView.swift b/apps/ios/Koins/Views/SettingsView.swift index b6dbd2e..26654a0 100644 --- a/apps/ios/Koins/Views/SettingsView.swift +++ b/apps/ios/Koins/Views/SettingsView.swift @@ -3,6 +3,7 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject private var model: AppModel @Environment(\.dismiss) private var dismiss + @State private var showDeleteConfirm = false private let currencies = ["USD", "MYR", "JPY"] @@ -63,11 +64,46 @@ struct SettingsView: View { } } + Card { + VStack(alignment: .leading, spacing: 10) { + Text("Danger zone") + .font(.system(size: 13, weight: .semibold)) + .foregroundStyle(Theme.muted) + Button { + showDeleteConfirm = true + } label: { + Text("Delete this wallet") + .font(.system(size: 15, weight: .semibold)) + .foregroundStyle(Theme.danger) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Theme.danger.opacity(0.12)) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + } + Text("Removes the wallet from this device. Your recovery phrase is the only way to get it back.") + .font(.system(size: 11)) + .foregroundStyle(Theme.muted) + } + } + Spacer(minLength: 0) } .padding(.horizontal, 20) .padding(.top, 12) } .preferredColorScheme(.dark) + .confirmationDialog("Delete this wallet?", + isPresented: $showDeleteConfirm, + titleVisibility: .visible) { + Button("Delete wallet", role: .destructive) { + Task { + await model.deleteWallet() + dismiss() + } + } + Button("Cancel", role: .cancel) {} + } message: { + Text("The keys stored on this device are removed. Make sure your recovery phrase is backed up.") + } } } \ No newline at end of file