secant-ios-wallet/modules/Sources/Features/SendFlow/SendFlowView.swift

192 lines
8.2 KiB
Swift

//
// SendFlowView.swift
// secant-testnet
//
// Created by Lukáš Korba on 04/25/2022.
//
import SwiftUI
import ComposableArchitecture
import Generated
import Scan
import UIComponents
import BalanceFormatter
public struct SendFlowView: View {
private enum InputID: Hashable {
case message
}
let store: SendFlowStore
let tokenName: String
@FocusState private var isAddressFocused
@FocusState private var isAmountFocused
@FocusState private var isMessageFocused
public init(store: SendFlowStore, tokenName: String) {
self.store = store
self.tokenName = tokenName
}
public var body: some View {
ZStack {
WithViewStore(store, observe: { $0 }) { viewStore in
ScrollView {
ScrollViewReader { value in
VStack(alignment: .center) {
BalanceWithIconView(balance: viewStore.totalBalance)
.padding(.top, 40)
.padding(.bottom, 5)
AvailableBalanceView(
balance: viewStore.shieldedBalance,
tokenName: tokenName
)
VStack(alignment: .leading) {
VStack(alignment: .leading) {
TransactionAddressTextField(
store: store.scope(
state: \.transactionAddressInputState,
action: SendFlowReducer.Action.transactionAddressInput
)
)
.frame(height: 63)
.focused($isAddressFocused)
.submitLabel(.next)
.onSubmit {
isAmountFocused = true
}
if viewStore.isInvalidAddressFormat {
Text(L10n.Send.Error.invalidAddress)
.foregroundColor(Asset.Colors.error.color)
.font(.custom(FontFamily.Inter.regular.name, size: 12))
}
}
.padding(.bottom, 20)
VStack(alignment: .leading) {
TransactionAmountTextField(
store: store.scope(
state: \.transactionAmountInputState,
action: SendFlowReducer.Action.transactionAmountInput
),
tokenName: tokenName
)
.frame(height: 63)
.focused($isAmountFocused)
.submitLabel(viewStore.isMemoInputEnabled ? .next : .return)
.onSubmit {
if viewStore.isMemoInputEnabled {
isMessageFocused = true
}
}
if viewStore.isInvalidAmountFormat {
Text(L10n.Send.Error.invalidAmount)
.foregroundColor(Asset.Colors.error.color)
.font(.custom(FontFamily.Inter.regular.name, size: 12))
} else if viewStore.isInsufficientFunds {
Text(L10n.Send.Error.insufficientFunds)
.foregroundColor(Asset.Colors.error.color)
.font(.custom(FontFamily.Inter.regular.name, size: 12))
}
}
.padding(.bottom, 20)
}
.padding(.top, 40)
MessageEditor(store: store.memoStore())
.frame(height: 140)
.disabled(!viewStore.isMemoInputEnabled)
.focused($isMessageFocused)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button(L10n.General.done.uppercased()) {
isAmountFocused = false
isMessageFocused = false
isAddressFocused = false
}
.foregroundColor(Asset.Colors.primary.color)
.font(.custom(FontFamily.Inter.regular.name, size: 14))
}
}
.id(InputID.message)
Button {
viewStore.send(.reviewPressed)
} label: {
Text(L10n.Send.review.uppercased())
}
.zcashStyle()
.disabled(!viewStore.isValidForm || viewStore.isSending)
.padding(.top, 40)
.padding(.horizontal, 30)
Text(viewStore.feeFormat)
.font(.custom(FontFamily.Inter.semiBold.name, size: 11))
.padding(.top, 20)
}
.padding(.horizontal, 30)
.onChange(of: isMessageFocused) { update in
withAnimation {
if update {
value.scrollTo(InputID.message, anchor: .center)
}
}
}
}
.onAppear { viewStore.send(.onAppear) }
.onDisappear { viewStore.send(.onDisappear) }
.applyScreenBackground()
.navigationLinkEmpty(
isActive: viewStore.bindingForScanQR,
destination: {
ScanView(store: store.scanStore())
}
)
.navigationLinkEmpty(
isActive: viewStore.bindingForSendConfirmation,
destination: {
SendFlowConfirmationView(store: store, tokenName: tokenName)
}
)
}
}
}
.padding(.vertical, 1)
.applyScreenBackground()
.alert(store: store.scope(
state: \.$alert,
action: { .alert($0) }
))
}
}
// MARK: - Previews
#Preview {
NavigationView {
SendFlowView(
store: .init(
initialState: .init(
addMemoState: true,
destination: nil,
memoState: .initial,
scanState: .initial,
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
)
) {
SendFlowReducer()
},
tokenName: "ZEC"
)
}
.navigationViewStyle(.stack)
}