Implement bank transfer app flows
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user