197 lines
7.3 KiB
Swift
197 lines
7.3 KiB
Swift
import SwiftUI
|
|
|
|
// Mirrors fragment_accounts.xml (RecyclerView) + item_account.xml layout:
|
|
// [40dp circle logo] [Name / Number(mono) / Type] [Balance + blocked + send button]
|
|
// Divider between items, grouped by bank with section headers.
|
|
struct AccountsView: View {
|
|
@Environment(HomeViewModel.self) private var vm
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Group {
|
|
if vm.accounts.isEmpty && !vm.isRefreshing {
|
|
emptyState
|
|
} else {
|
|
accountList
|
|
}
|
|
}
|
|
.navigationTitle("Accounts")
|
|
.toolbar {
|
|
if vm.isRefreshing {
|
|
ToolbarItem(placement: .topBarTrailing) { ProgressView() }
|
|
}
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button {
|
|
withAnimation { vm.hideAmounts.toggle() }
|
|
} label: {
|
|
Image(systemName: vm.hideAmounts ? "eye.slash" : "eye")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Account list
|
|
|
|
private var accountList: some View {
|
|
List {
|
|
bankSection(title: "MIB Faisanet", accounts: vm.mibAccounts, color: bankColor("MIB"))
|
|
bankSection(title: "Bank of Maldives", accounts: vm.bmlAccounts, color: bankColor("BML"))
|
|
bankSection(title: "Fahipay", accounts: vm.fahipayAccounts, color: bankColor("FAHIPAY"))
|
|
}
|
|
.listStyle(.plain)
|
|
.refreshable { await vm.refresh() }
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func bankSection(title: String, accounts: [BankAccount], color: Color) -> some View {
|
|
if !accounts.isEmpty {
|
|
Section {
|
|
ForEach(accounts) { account in
|
|
AccountRow(account: account, hideAmounts: vm.hideAmounts)
|
|
.listRowInsets(EdgeInsets()) // remove default insets — row provides its own
|
|
.listRowSeparator(.hidden)
|
|
}
|
|
} header: {
|
|
HStack(spacing: 6) {
|
|
Circle().fill(color).frame(width: 8, height: 8)
|
|
Text(title)
|
|
.font(.caption.weight(.semibold))
|
|
.foregroundStyle(.secondary)
|
|
.textCase(nil)
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.top, 8)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Empty state
|
|
|
|
private var emptyState: some View {
|
|
VStack(spacing: 16) {
|
|
Image(systemName: "building.columns")
|
|
.font(.system(size: 52))
|
|
.foregroundStyle(.tertiary)
|
|
Text("No accounts yet")
|
|
.font(.headline)
|
|
Text("Your accounts will appear here after logging in to a bank.")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal)
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
}
|
|
|
|
// MARK: - Account row (item_account.xml)
|
|
// Layout: [40dp circle] [Name / Number(mono) / Type] → [Balance / Blocked / SendBtn(40dp)]
|
|
// Padding: 16dp horizontal, 14dp vertical. Divider below.
|
|
|
|
private struct AccountRow: View {
|
|
let account: BankAccount
|
|
let hideAmounts: Bool
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack(alignment: .center, spacing: 0) {
|
|
// ── Bank logo circle (ShapeableImageView 40dp) ──────────────────
|
|
bankLogo
|
|
.padding(.trailing, 12)
|
|
|
|
// ── Left: name / number / type ──────────────────────────────────
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(displayName)
|
|
.font(.subheadline.weight(.medium)) // textAppearanceTitleMedium
|
|
.foregroundStyle(.primary)
|
|
.lineLimit(1)
|
|
|
|
Text(account.accountNumber)
|
|
.font(.callout) // textAppearanceTitleSmall
|
|
.foregroundStyle(.secondary)
|
|
.fontDesign(.monospaced) // android:fontFamily="monospace"
|
|
.lineLimit(1)
|
|
|
|
if !account.accountTypeName.isEmpty {
|
|
Text(account.accountTypeName)
|
|
.font(.caption) // textAppearanceBodySmall
|
|
.foregroundStyle(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
|
|
Spacer(minLength: 16)
|
|
|
|
// ── Right: balance / blocked / send button ──────────────────────
|
|
VStack(alignment: .trailing, spacing: 2) {
|
|
Text(hideAmounts ? "••••••" : account.formattedAvailableBalance)
|
|
.font(.callout.weight(.medium)) // textAppearanceTitleSmall
|
|
.foregroundStyle(.primary)
|
|
.monospacedDigit()
|
|
.lineLimit(1)
|
|
|
|
if account.blockedAmount > 0 && !hideAmounts {
|
|
Text("Blocked: \(account.currencyName) \(String(format: "%,.2f", account.blockedAmount))")
|
|
.font(.caption2)
|
|
.foregroundStyle(.red)
|
|
.lineLimit(1)
|
|
}
|
|
|
|
// Transfer button (40x40dp, borderless, tinted primary)
|
|
Button { /* Transfer — Phase 7 */ } label: {
|
|
Image(systemName: "arrow.up.right")
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundStyle(.tint)
|
|
.frame(width: 40, height: 40)
|
|
.contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
.padding(.top, 4)
|
|
}
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 14)
|
|
|
|
// Divider (1dp, 16dp horizontal margin, colorOutlineVariant)
|
|
Divider()
|
|
.padding(.horizontal, 16)
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private var displayName: String {
|
|
account.accountBriefName.isEmpty ? account.accountTypeName : account.accountBriefName
|
|
}
|
|
|
|
private var bankLogo: some View {
|
|
Circle()
|
|
.fill(color.opacity(0.12))
|
|
.frame(width: 40, height: 40)
|
|
.overlay {
|
|
Text(String(account.bank.prefix(1)))
|
|
.font(.headline)
|
|
.foregroundStyle(color)
|
|
}
|
|
}
|
|
|
|
private var color: Color { bankColor(account.bank) }
|
|
}
|
|
|
|
// MARK: - Shared bank color
|
|
|
|
private func bankColor(_ bank: String) -> Color {
|
|
switch bank {
|
|
case "MIB": return Color(red: 0.247, green: 0.396, blue: 0.678) // #3F65AD primary
|
|
case "BML": return Color(red: 0.0, green: 0.47, blue: 0.80)
|
|
case "FAHIPAY": return .purple
|
|
default: return .gray
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
AccountsView()
|
|
.environment(HomeViewModel())
|
|
}
|