- Address details have been extended to show QR codes for the addresses - Profile + Address views are now scroll views - Send feature now supports scan QR feature, when valid zcash address is scanned the address is automatically filled - snapshot tests & unit tests fixed and extended accordingly
This commit is contained in:
parent
64d509aedb
commit
f978565f38
|
@ -13,8 +13,11 @@ struct AddressDetailsView: View {
|
|||
|
||||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
VStack {
|
||||
ScrollView {
|
||||
Text("Unified Address")
|
||||
.fontWeight(.bold)
|
||||
qrCode(viewStore.unifiedAddress)
|
||||
.padding(30)
|
||||
|
||||
Text("\(viewStore.unifiedAddress)")
|
||||
.onTapGesture {
|
||||
|
@ -22,7 +25,10 @@ struct AddressDetailsView: View {
|
|||
}
|
||||
|
||||
Text("Sapling Address")
|
||||
.fontWeight(.bold)
|
||||
.padding(.top, 20)
|
||||
qrCode(viewStore.saplingAddress)
|
||||
.padding(30)
|
||||
|
||||
Text("\(viewStore.saplingAddress)")
|
||||
.onTapGesture {
|
||||
|
@ -30,7 +36,10 @@ struct AddressDetailsView: View {
|
|||
}
|
||||
|
||||
Text("Transparent Address")
|
||||
.fontWeight(.bold)
|
||||
.padding(.top, 20)
|
||||
qrCode(viewStore.transparentAddress)
|
||||
.padding(30)
|
||||
|
||||
Text("\(viewStore.transparentAddress)")
|
||||
.onTapGesture {
|
||||
|
@ -43,6 +52,29 @@ struct AddressDetailsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
extension AddressDetailsView {
|
||||
func qrCode(_ qrText: String) -> some View {
|
||||
Group {
|
||||
if let img = QRCodeGenerator.generate(from: qrText) {
|
||||
Image(img, scale: 1, label: Text(String(format: NSLocalizedString("QR Code for %@", comment: ""), "\(qrText)") ))
|
||||
.cornerRadius(20)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.stroke(Color.white, lineWidth: 25)
|
||||
.scaleEffect(1.1)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.stroke(Color.black, lineWidth: 8)
|
||||
.scaleEffect(1.1)
|
||||
)
|
||||
} else {
|
||||
Image(systemName: "qrcode")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddressDetails_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddressDetailsView(store: .placeholder)
|
||||
|
|
|
@ -6,7 +6,7 @@ struct ProfileView: View {
|
|||
|
||||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
VStack {
|
||||
ScrollView {
|
||||
qrCodeUA(viewStore.unifiedAddress)
|
||||
.padding(.top, 30)
|
||||
|
||||
|
|
|
@ -19,15 +19,17 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
enum Destination: Equatable {
|
||||
case confirmation
|
||||
case inProgress
|
||||
case scanQR
|
||||
case success
|
||||
case failure
|
||||
case done
|
||||
}
|
||||
|
||||
var addMemoState: Bool
|
||||
var destination: Destination?
|
||||
var isSendingTransaction = false
|
||||
var memoState: MultiLineTextFieldReducer.State
|
||||
var destination: Destination?
|
||||
var scanState: ScanReducer.State
|
||||
var shieldedBalance = WalletBalance.zero
|
||||
var transactionAddressInputState: TransactionAddressTextFieldReducer.State
|
||||
var transactionAmountInputState: TransactionAmountTextFieldReducer.State
|
||||
|
@ -78,6 +80,7 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
case memo(MultiLineTextFieldReducer.Action)
|
||||
case onAppear
|
||||
case onDisappear
|
||||
case scan(ScanReducer.Action)
|
||||
case sendConfirmationPressed
|
||||
case sendTransactionResult(Result<TransactionState, NSError>)
|
||||
case synchronizerStateChanged(SDKSynchronizerState)
|
||||
|
@ -86,6 +89,7 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
case updateDestination(SendFlowReducer.State.Destination?)
|
||||
}
|
||||
|
||||
@Dependency(\.audioServices) var audioServices
|
||||
@Dependency(\.derivationTool) var derivationTool
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.mnemonic) var mnemonic
|
||||
|
@ -110,6 +114,10 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
TransactionAmountTextFieldReducer()
|
||||
}
|
||||
|
||||
Scope(state: \.scanState, action: /Action.scan) {
|
||||
ScanReducer()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .addMemo:
|
||||
|
@ -188,6 +196,9 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
case .transactionAmountInput:
|
||||
return .none
|
||||
|
||||
case .transactionAddressInput(.scanQR):
|
||||
return Effect(value: .updateDestination(.scanQR))
|
||||
|
||||
case .transactionAddressInput:
|
||||
return .none
|
||||
|
||||
|
@ -213,6 +224,17 @@ struct SendFlowReducer: ReducerProtocol {
|
|||
|
||||
case .memo:
|
||||
return .none
|
||||
|
||||
case .scan(.found(let address)):
|
||||
state.transactionAddressInputState.textFieldState.text = address
|
||||
// The is valid Zcash address check is already covered in the scan feature
|
||||
// so we can be sure it's valid and thus `true` value here.
|
||||
state.transactionAddressInputState.isValidAddress = true
|
||||
audioServices.systemSoundVibrate()
|
||||
return Effect(value: .updateDestination(nil))
|
||||
|
||||
case .scan:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,13 +249,20 @@ extension SendFlowStore {
|
|||
action: SendFlowReducer.Action.addMemo
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func memoStore() -> MultiLineTextFieldStore {
|
||||
self.scope(
|
||||
state: \.memoState,
|
||||
action: SendFlowReducer.Action.memo
|
||||
)
|
||||
}
|
||||
|
||||
func scanStore() -> ScanStore {
|
||||
self.scope(
|
||||
state: \.scanState,
|
||||
action: SendFlowReducer.Action.scan
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewStore
|
||||
|
@ -282,6 +311,15 @@ extension SendFlowViewStore {
|
|||
embed: { _ in SendFlowReducer.State.Destination.failure }
|
||||
)
|
||||
}
|
||||
|
||||
var bindingForScanQR: Binding<Bool> {
|
||||
self.destinationBinding.map(
|
||||
extract: {
|
||||
$0 == .scanQR
|
||||
},
|
||||
embed: { $0 ? SendFlowReducer.State.Destination.scanQR : nil }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Placeholders
|
||||
|
@ -290,8 +328,9 @@ extension SendFlowReducer.State {
|
|||
static var placeholder: Self {
|
||||
.init(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
destination: nil,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .amount
|
||||
)
|
||||
|
@ -300,8 +339,9 @@ extension SendFlowReducer.State {
|
|||
static var emptyPlaceholder: Self {
|
||||
.init(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
destination: nil,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .placeholder
|
||||
)
|
||||
|
|
|
@ -22,6 +22,12 @@ struct SendFlowView: View {
|
|||
TransactionConfirmation(store: store)
|
||||
}
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForScanQR,
|
||||
destination: {
|
||||
ScanView(store: store.scanStore())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +41,9 @@ struct SendFLowView_Previews: PreviewProvider {
|
|||
store: .init(
|
||||
initialState: .init(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
destination: nil,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .placeholder
|
||||
),
|
||||
|
|
|
@ -36,9 +36,13 @@ struct TransactionAddressTextField: View {
|
|||
},
|
||||
inputPrefixView: { EmptyView() },
|
||||
inputAccessoryView: {
|
||||
Image(Asset.Assets.Icons.qrCode.name)
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
Button {
|
||||
viewStore.send(.scanQR)
|
||||
} label: {
|
||||
Image(Asset.Assets.Icons.qrCode.name)
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ struct TransactionAddressTextFieldReducer: ReducerProtocol {
|
|||
|
||||
enum Action: Equatable {
|
||||
case clearAddress
|
||||
case scanQR
|
||||
case textField(TCATextFieldReducer.Action)
|
||||
}
|
||||
|
||||
|
@ -29,6 +30,9 @@ struct TransactionAddressTextFieldReducer: ReducerProtocol {
|
|||
case .clearAddress:
|
||||
state.textFieldState.text = ""
|
||||
return .none
|
||||
|
||||
case .scanQR:
|
||||
return .none
|
||||
|
||||
case .textField(.set(let address)):
|
||||
do {
|
||||
|
|
|
@ -300,6 +300,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldReducer.State(
|
||||
|
@ -339,6 +340,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldReducer.State(
|
||||
|
@ -397,6 +399,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldReducer.State(
|
||||
|
@ -436,6 +439,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldReducer.State(
|
||||
|
@ -474,6 +478,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldReducer.State(
|
||||
|
@ -511,6 +516,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldReducer.State(
|
||||
|
@ -549,6 +555,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: MultiLineTextFieldReducer.State(charLimit: 3),
|
||||
scanState: .placeholder,
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(1), total: Zatoshi(1)),
|
||||
transactionAddressInputState:
|
||||
TransactionAddressTextFieldReducer.State(
|
||||
|
@ -590,6 +597,7 @@ class SendTests: XCTestCase {
|
|||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState:
|
||||
TransactionAmountTextFieldReducer.State(
|
||||
|
@ -618,6 +626,32 @@ class SendTests: XCTestCase {
|
|||
// .onDisappear cancels it, must have for the test to pass
|
||||
store.send(.onDisappear)
|
||||
}
|
||||
|
||||
func testScannedAddress() throws {
|
||||
let sendState = SendFlowReducer.State(
|
||||
addMemoState: true,
|
||||
memoState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
transactionAddressInputState: .placeholder,
|
||||
transactionAmountInputState: .placeholder
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: sendState,
|
||||
reducer: SendFlowReducer()
|
||||
)
|
||||
|
||||
store.dependencies.audioServices = AudioServicesClient(systemSoundVibrate: { })
|
||||
|
||||
// We don't need to pass a valid address here, we just need to confirm some
|
||||
// found string is received and the `isValidAddress` flag is set to `true`
|
||||
store.send(.scan(.found("address"))) { state in
|
||||
state.transactionAddressInputState.textFieldState.text = "address"
|
||||
state.transactionAddressInputState.isValidAddress = true
|
||||
}
|
||||
|
||||
store.receive(.updateDestination(nil))
|
||||
}
|
||||
}
|
||||
|
||||
private extension SendTests {
|
||||
|
|
Loading…
Reference in New Issue