[#73] [Scaffold] Profile Screen (#386)

- qr code generator
- qr, address and initial buttons in place

[73] [Scaffold] Profile Screen

- address details view placeholder
- navigation to address details view
- custom back
- app build + version from environment

[73] [Scaffold] Profile Screen

- unit tests

[73] [Scaffold] Profile Screen

- snapshot of the profile view

[73] [Scaffold] Profile Screen (386)

- comments resolved
- AppVersionHandler implemented
- sdkVersion also in the UI
- updated profile view design according to MVP requirements

[73] [Scaffold] Profile Screen (386)

- AppVersionHandler added to the repo
This commit is contained in:
Lukas Korba 2022-07-06 18:54:30 +02:00 committed by GitHub
parent bf6e9bfbb0
commit 59650d723d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 498 additions and 80 deletions

View File

@ -121,6 +121,12 @@
9E5BF6502823E94900BA3F17 /* TransactionAddressTextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */; };
9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; };
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; };
9E7CB61A287310EC00A02233 /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */; };
9E7CB6202874143800A02233 /* AddressDetailsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB61F2874143800A02233 /* AddressDetailsStore.swift */; };
9E7CB6212874143800A02233 /* AddressDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB61E2874143800A02233 /* AddressDetailsView.swift */; };
9E7CB6242874246800A02233 /* ProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6232874246800A02233 /* ProfileTests.swift */; };
9E7CB6272874269F00A02233 /* ProfileSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6262874269F00A02233 /* ProfileSnapshotTests.swift */; };
9E7CB6292875AC2D00A02233 /* AppVersionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6282875AC2D00A02233 /* AppVersionHandler.swift */; };
9E7FE0CF282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */; };
9E7FE0D3282D274E00C374E8 /* Date+Readable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */; };
9E7FE0D5282D281800C374E8 /* Array+Chunked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */; };
@ -331,6 +337,12 @@
9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextFieldStore.swift; sourceTree = "<group>"; };
9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = "<group>"; };
9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsSnapshotTests.swift; sourceTree = "<group>"; };
9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
9E7CB61E2874143800A02233 /* AddressDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDetailsView.swift; sourceTree = "<group>"; };
9E7CB61F2874143800A02233 /* AddressDetailsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDetailsStore.swift; sourceTree = "<group>"; };
9E7CB6232874246800A02233 /* ProfileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTests.swift; sourceTree = "<group>"; };
9E7CB6262874269F00A02233 /* ProfileSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSnapshotTests.swift; sourceTree = "<group>"; };
9E7CB6282875AC2D00A02233 /* AppVersionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHandler.swift; sourceTree = "<group>"; };
9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SDKSynchronizer+SyncStatus.swift"; sourceTree = "<group>"; };
9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Readable.swift"; sourceTree = "<group>"; };
9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Chunked.swift"; sourceTree = "<group>"; };
@ -516,8 +528,9 @@
0D4E7A1926B364180058B01E /* secantTests */ = {
isa = PBXGroup;
children = (
9EAB4674285B5C68002904A0 /* DeeplinkTests */,
9E391162284E3ECF0073DD9A /* SnapshotTests */,
9E7CB6222874245400A02233 /* ProfileTests */,
9EAB4674285B5C68002904A0 /* DeeplinkTests */,
9E3911372848AD3A0073DD9A /* HomeTests */,
9E391122283E4C970073DD9A /* ImportWalletTests */,
9E01F8262833CD84000EFC57 /* ScanTests */,
@ -676,6 +689,7 @@
F9971A4927680DC400A2DB75 /* App */,
F93874EC273C4DE200F0E875 /* Home */,
F9971A4F27680DD000A2DB75 /* Profile */,
9E7CB61B2874140900A02233 /* AddressDetails */,
F9971A5527680DDE00A2DB75 /* Request */,
F9971A5B27680DF600A2DB75 /* Scan */,
F9C165B62740403600592F76 /* SendFlow */,
@ -812,6 +826,7 @@
9E391162284E3ECF0073DD9A /* SnapshotTests */ = {
isa = PBXGroup;
children = (
9E7CB6252874267B00A02233 /* ProfileSnapshotTests */,
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */,
9E9ECC8B28589E150099D5A2 /* HomeSnapshotTests */,
9E9ECC9328589E150099D5A2 /* ImportWalletSnapshotTests */,
@ -869,6 +884,31 @@
path = WalletEventsSnapshotTests;
sourceTree = "<group>";
};
9E7CB61B2874140900A02233 /* AddressDetails */ = {
isa = PBXGroup;
children = (
9E7CB61F2874143800A02233 /* AddressDetailsStore.swift */,
9E7CB61E2874143800A02233 /* AddressDetailsView.swift */,
);
path = AddressDetails;
sourceTree = "<group>";
};
9E7CB6222874245400A02233 /* ProfileTests */ = {
isa = PBXGroup;
children = (
9E7CB6232874246800A02233 /* ProfileTests.swift */,
);
path = ProfileTests;
sourceTree = "<group>";
};
9E7CB6252874267B00A02233 /* ProfileSnapshotTests */ = {
isa = PBXGroup;
children = (
9E7CB6262874269F00A02233 /* ProfileSnapshotTests.swift */,
);
path = ProfileSnapshotTests;
sourceTree = "<group>";
};
9E7FE0B6282D1D9800C374E8 /* Resources */ = {
isa = PBXGroup;
children = (
@ -908,6 +948,7 @@
2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */,
9E2F1C832809B606004E65FE /* DebugMenu.swift */,
9E391128283F74590073DD9A /* Zatoshi.swift */,
9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -922,6 +963,7 @@
9E3911472848EEB90073DD9A /* WalletStorage.swift */,
9E3911452848EEB90073DD9A /* ZCashSDKEnvironment.swift */,
9EAB4670285A1C77002904A0 /* DeeplinkHandler.swift */,
9E7CB6282875AC2D00A02233 /* AppVersionHandler.swift */,
);
path = Dependencies;
sourceTree = "<group>";
@ -1423,6 +1465,7 @@
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
9E02B56A27FED43E005B809B /* WrappedFileManager.swift in Sources */,
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
9E7CB6202874143800A02233 /* AddressDetailsStore.swift in Sources */,
0DC487C32772574C00BE6A63 /* RecoveryPhraseBackupSucceededView.swift in Sources */,
2EB1C5E827D77F6100BC43D7 /* TCATextFieldStore.swift in Sources */,
9E5BF648282277BE00BA3F17 /* WrappedNotificationCenter.swift in Sources */,
@ -1430,6 +1473,7 @@
9E5BF641281FD7B600BA3F17 /* TransactionFailedView.swift in Sources */,
9E7FE0CF282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift in Sources */,
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */,
9E7CB6292875AC2D00A02233 /* AppVersionHandler.swift in Sources */,
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
9EAB467A2861EA6A002904A0 /* TransactionRowView.swift in Sources */,
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidationFlowStore.swift in Sources */,
@ -1487,6 +1531,7 @@
9E7FE0E6282E7B1100C374E8 /* StoredWallet.swift in Sources */,
9EAFEB9128081E9400199FC9 /* HomeStore.swift in Sources */,
F9971A5A27680DDE00A2DB75 /* RequestView.swift in Sources */,
9E7CB61A287310EC00A02233 /* QRCodeGenerator.swift in Sources */,
9E7FE0D5282D281800C374E8 /* Array+Chunked.swift in Sources */,
0DACFA8127208D940039EEA5 /* UInt+SuperscriptText.swift in Sources */,
0DF2DC51272344E400FA31E2 /* EmptyChip.swift in Sources */,
@ -1534,6 +1579,7 @@
9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */,
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */,
2E8719CB27FB09990082C926 /* TransactionAmountTextField.swift in Sources */,
9E7CB6212874143800A02233 /* AddressDetailsView.swift in Sources */,
F9C165C42740403600592F76 /* TransactionSentView.swift in Sources */,
F9971A5927680DDE00A2DB75 /* RequestStore.swift in Sources */,
);
@ -1547,11 +1593,13 @@
9EDDEAA22829610D00B4100C /* CurrencySelectionTests.swift in Sources */,
9E01F8282833CDA0000EFC57 /* ScanTests.swift in Sources */,
9EDDEAA42829610D00B4100C /* TransactionAddressInputTests.swift in Sources */,
9E7CB6272874269F00A02233 /* ProfileSnapshotTests.swift in Sources */,
9E92AF0828530EBF007367AD /* View+UIImage.swift in Sources */,
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
9E39112E283F91600073DD9A /* ZatoshiTests.swift in Sources */,
9E9ECC9B28589E150099D5A2 /* ImportWalletSnapshotTests.swift in Sources */,
9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */,
9E7CB6242874246800A02233 /* ProfileTests.swift in Sources */,
9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */,
9E9ECC9828589E150099D5A2 /* WelcomeSnapshotTests.swift in Sources */,
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */,

View File

@ -0,0 +1,25 @@
//
// AppVersionHandler.swift
// secant-testnet
//
// Created by Lukáš Korba on 06.07.2022.
//
import Foundation
struct AppVersionHandler {
let appVersion: () -> String
let appBuild: () -> String
}
extension AppVersionHandler {
static let live = AppVersionHandler(
appVersion: { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" },
appBuild: { Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" }
)
static let test = AppVersionHandler(
appVersion: { "0.0.1" },
appBuild: { "31" }
)
}

View File

@ -26,6 +26,7 @@ struct ZCashSDKEnvironment {
let mnemonicWordsMaxCount: Int
let network: ZcashNetwork
let requiredTransactionConfirmations: Int
let sdkVersion: String
}
extension ZCashSDKEnvironment {
@ -38,7 +39,8 @@ extension ZCashSDKEnvironment {
),
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
network: ZcashNetworkBuilder.network(for: .mainnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.14.0-beta"
)
static let testnet = ZCashSDKEnvironment(
@ -50,6 +52,7 @@ extension ZCashSDKEnvironment {
),
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
network: ZcashNetworkBuilder.network(for: .testnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.14.0-beta"
)
}

View File

@ -0,0 +1,80 @@
//
// AddressDetailsStore.swift
// secant-testnet
//
// Created by Lukáš Korba on 05.07.2022.
//
import Foundation
import ComposableArchitecture
typealias AddressDetailsReducer = Reducer<AddressDetailsState, AddressDetailsAction, AddressDetailsEnvironment>
typealias AddressDetailsStore = Store<AddressDetailsState, AddressDetailsAction>
typealias AddressDetailsViewStore = ViewStore<AddressDetailsState, AddressDetailsAction>
// MARK: - State
struct AddressDetailsState: Equatable {
}
// MARK: - Action
enum AddressDetailsAction: Equatable {
case copyToPastboard(String)
}
// MARK: - Environment
struct AddressDetailsEnvironment {
let pasteboard: WrappedPasteboard
}
extension AddressDetailsEnvironment {
static let live = AddressDetailsEnvironment(
pasteboard: .live
)
static let mock = AddressDetailsEnvironment(
pasteboard: .test
)
}
// MARK: - Reducer
extension AddressDetailsReducer {
static let `default` = AddressDetailsReducer { _, action, environment in
switch action {
case .copyToPastboard(let value):
environment.pasteboard.setString(value)
}
return .none
}
}
// MARK: - Store
extension AddressDetailsStore {
}
// MARK: - ViewStore
extension AddressDetailsViewStore {
}
// MARK: - Placeholders
extension AddressDetailsState {
static let placeholder = AddressDetailsState(
)
}
extension AddressDetailsStore {
static let placeholder = AddressDetailsStore(
initialState: .placeholder,
reducer: .default,
environment: AddressDetailsEnvironment(
pasteboard: .test
)
)
}

View File

@ -0,0 +1,25 @@
//
// AddressDetailsView.swift
// secant-testnet
//
// Created by Lukáš Korba on 05.07.2022.
//
import SwiftUI
import ComposableArchitecture
struct AddressDetails: View {
let store: AddressDetailsStore
var body: some View {
WithViewStore(store) { _ in
Text("Address Details")
}
}
}
struct AddressDetails_Previews: PreviewProvider {
static var previews: some View {
AddressDetails(store: .placeholder)
}
}

View File

@ -18,7 +18,7 @@ struct AppView: View {
)
)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationViewStyle(.stack)
case .sandbox:
NavigationView {
@ -29,7 +29,7 @@ struct AppView: View {
)
)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationViewStyle(.stack)
case .onboarding:
NavigationView {
@ -40,7 +40,7 @@ struct AppView: View {
)
)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationViewStyle(.stack)
case .startup:
ZStack(alignment: .topTrailing) {
@ -57,7 +57,7 @@ struct AppView: View {
)
)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationViewStyle(.stack)
case .phraseDisplay:
NavigationView {

View File

@ -144,6 +144,10 @@ extension HomeReducer {
state.route = route
return .none
case .profile(.back):
state.route = nil
return .none
case .profile(let action):
return .none
@ -171,7 +175,7 @@ extension HomeReducer {
case .scan(let action):
return .none
case .debugMenuStartup:
return .none
}
@ -222,8 +226,11 @@ extension HomeReducer {
action: /HomeAction.profile,
environment: { environment in
ProfileEnvironment(
appVersionHandler: .live,
mnemonic: environment.mnemonic,
walletStorage: environment.walletStorage
SDKSynchronizer: environment.SDKSynchronizer,
walletStorage: environment.walletStorage,
zcashSDKEnvironment: environment.zcashSDKEnvironment
)
}
)

View File

@ -9,74 +9,97 @@ typealias ProfileViewStore = ViewStore<ProfileState, ProfileAction>
struct ProfileState: Equatable {
enum Route {
case phraseDisplay
case addressDetails
case settings
case walletInfo
}
var phraseDisplayState: RecoveryPhraseDisplayState
var address = ""
var addressDetailsState: AddressDetailsState
var appBuild = ""
var appVersion = ""
var route: Route?
var sdkVersion = ""
var settingsState: SettingsState
var walletInfoState: WalletInfoState
}
// MARK: - Action
enum ProfileAction: Equatable {
case phraseDisplay(RecoveryPhraseDisplayAction)
case addressDetails(AddressDetailsAction)
case back
case onAppear
case updateRoute(ProfileState.Route?)
}
// MARK: - Environment
struct ProfileEnvironment {
let appVersionHandler: AppVersionHandler
let mnemonic: WrappedMnemonic
let SDKSynchronizer: WrappedSDKSynchronizer
let walletStorage: WrappedWalletStorage
let zcashSDKEnvironment: ZCashSDKEnvironment
}
extension ProfileEnvironment {
static let live = ProfileEnvironment(
appVersionHandler: .live,
mnemonic: .live,
walletStorage: .live()
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
walletStorage: .live(),
zcashSDKEnvironment: .mainnet
)
static let mock = ProfileEnvironment(
appVersionHandler: .test,
mnemonic: .mock,
walletStorage: .live()
SDKSynchronizer: MockWrappedSDKSynchronizer(),
walletStorage: .live(),
zcashSDKEnvironment: .testnet
)
}
// MARK: - Reducer
extension ProfileReducer {
static let `default` = ProfileReducer { state, action, environment in
switch action {
case .updateRoute(.phraseDisplay):
do {
let storedWallet = try environment.walletStorage.exportWallet()
let phraseWords = try environment.mnemonic.asWords(storedWallet.seedPhrase)
let recoveryPhrase = RecoveryPhrase(words: phraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
state.route = .phraseDisplay
} catch {
// TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
return .none
}
return .none
static let `default` = ProfileReducer.combine(
[
profileReducer,
addressDetailsReducer
]
)
.debug()
private static let profileReducer = ProfileReducer { state, action, environment in
switch action {
case .onAppear:
state.address = environment.SDKSynchronizer.getShieldedAddress() ?? ""
state.appBuild = environment.appVersionHandler.appBuild()
state.appVersion = environment.appVersionHandler.appVersion()
state.sdkVersion = environment.zcashSDKEnvironment.sdkVersion
return .none
case .back:
return .none
case let .updateRoute(route):
state.route = route
return .none
case .phraseDisplay(.finishedPressed):
state.route = nil
return .none
case .phraseDisplay:
case .addressDetails:
return .none
}
}
private static let addressDetailsReducer: ProfileReducer = AddressDetailsReducer.default.pullback(
state: \ProfileState.addressDetailsState,
action: /ProfileAction.addressDetails,
environment: { _ in
AddressDetailsEnvironment(
pasteboard: .live
)
}
)
}
// MARK: - ViewStore
@ -89,10 +112,10 @@ extension ProfileViewStore {
)
}
var bindingForWalletInfo: Binding<Bool> {
var bindingForAddressDetails: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .walletInfo },
embed: { $0 ? .walletInfo : nil }
extract: { $0 == .addressDetails },
embed: { $0 ? .addressDetails : nil }
)
}
@ -102,13 +125,6 @@ extension ProfileViewStore {
embed: { $0 ? .settings : nil }
)
}
var bindingForPhraseDisplay: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .phraseDisplay },
embed: { $0 ? .phraseDisplay : nil }
)
}
}
// MARK: Placeholders
@ -116,10 +132,9 @@ extension ProfileViewStore {
extension ProfileState {
static var placeholder: Self {
.init(
phraseDisplayState: .init(),
addressDetailsState: .placeholder,
route: nil,
settingsState: .init(),
walletInfoState: .init()
settingsState: .init()
)
}
}

View File

@ -6,37 +6,101 @@ struct ProfileView: View {
var body: some View {
WithViewStore(store) { viewStore in
List {
Text("Go To Wallet Info")
.navigationLink(
isActive: viewStore.bindingForWalletInfo,
destination: {
Text("Wallet")
}
)
VStack {
qrCodeUA(viewStore.address)
.padding(.top, 30)
Text("Your UA address \(viewStore.address)")
.truncationMode(.middle)
.multilineTextAlignment(.center)
.lineLimit(2)
.padding(30)
Text("Show me backup phrase")
.navigationLink(
isActive: viewStore.bindingForPhraseDisplay,
destination: {
RecoveryPhraseDisplayView(
store: store.scope(
state: \.phraseDisplayState,
action: ProfileAction.phraseDisplay
)
)
}
Button(
action: { viewStore.send(.updateRoute(.addressDetails)) },
label: { Text("See address details") }
)
.activeButtonStyle
.frame(height: 50)
.padding(EdgeInsets(top: 0, leading: 30, bottom: 50, trailing: 30))
Text("Go To Settings")
.navigationLink(
isActive: viewStore.bindingForSettings,
destination: {
Text("Settings")
}
Rectangle()
.frame(height: 1.5)
.padding(EdgeInsets(top: 0, leading: 100, bottom: 20, trailing: 100))
.foregroundColor(Asset.Colors.TextField.Underline.purple.color)
Button(
action: { viewStore.send(.updateRoute(.settings)) },
label: { Text("Settings") }
)
.primaryButtonStyle
.frame(height: 50)
.padding(EdgeInsets(top: 30, leading: 30, bottom: 20, trailing: 30))
Button(
action: { },
label: { Text("Support") }
)
.primaryButtonStyle
.frame(height: 50)
.padding(EdgeInsets(top: 0, leading: 30, bottom: 20, trailing: 30))
Spacer()
HStack {
VStack {
Text("secant v\(viewStore.appVersion)(\(viewStore.appBuild))")
Text("sdk v\(viewStore.sdkVersion)")
}
Spacer()
Button(
action: { },
label: {
Text("More info")
.foregroundColor(Asset.Colors.Text.moreInfoText.color)
}
)
}
.padding(30)
}
.onAppear(perform: { viewStore.send(.onAppear) })
.navigationLinkEmpty(
isActive: viewStore.bindingForSettings,
destination: {
SettingsView()
}
)
.navigationLinkEmpty(
isActive: viewStore.bindingForAddressDetails,
destination: {
AddressDetails(store: .placeholder)
}
)
}
.applyScreenBackground()
.navigationBarTitleDisplayMode(.inline)
}
}
extension ProfileView {
func qrCodeUA(_ 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")
}
.navigationTitle(Text("\(String(describing: Self.self))"))
}
}
}
@ -49,14 +113,14 @@ struct ProfileView_Previews: PreviewProvider {
ProfileView(
store: .init(
initialState: .init(
phraseDisplayState: .init(),
settingsState: .init(),
walletInfoState: .init()
addressDetailsState: .placeholder,
settingsState: .init()
),
reducer: .default,
environment: .live
)
)
}
.preferredColorScheme(.dark)
}
}

View File

@ -50,7 +50,7 @@ struct SendFLowView_Previews: PreviewProvider {
)
)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(StackNavigationViewStyle())
.navigationViewStyle(.stack)
}
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0xB8",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0xB8",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -116,6 +116,7 @@ internal enum Asset {
internal static let captionText = ColorAsset(name: "captionText")
internal static let captionTextShadow = ColorAsset(name: "captionTextShadow")
internal static let highlightedSuperscriptText = ColorAsset(name: "highlightedSuperscriptText")
internal static let moreInfoText = ColorAsset(name: "moreInfoText")
}
internal enum TextField {
internal static let titleAccessoryButton = ColorAsset(name: "TitleAccessoryButton")

View File

@ -0,0 +1,45 @@
//
// QRCodeGenerator.swift
// secant-testnet
//
// Created by Lukáš Korba on 04.07.2022.
//
import Foundation
import Combine
import CoreImage.CIFilterBuiltins
import SwiftUI
enum QRCodeGenerator {
enum QRCodeError: Error {
case failedToGenerate
}
static func generate(from string: String) -> Future<CGImage, QRCodeError> {
Future<CGImage, QRCodeError> { promise in
DispatchQueue.global().async {
guard let image = generate(from: string) else {
promise(.failure(QRCodeGenerator.QRCodeError.failedToGenerate))
return
}
return promise(.success(image))
}
}
}
static func generate(from string: String, scale: CGFloat = 5) -> CGImage? {
let data = string.data(using: String.Encoding.utf8)
let context = CIContext()
let filter = CoreImage.CIFilter.qrCodeGenerator()
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: scale, y: scale)
guard let output = filter.outputImage?.transformed(by: transform) else {
return nil
}
return context.createCGImage(output, from: output.extent)
}
}

View File

@ -469,7 +469,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func getTransparentAddress(account: Int) -> TransparentAddress? { nil }
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? { nil }
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? { "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8" }
func sendTransaction(
with spendingKey: String,

View File

@ -0,0 +1,35 @@
//
// ProfileTests.swift
// secantTests
//
// Created by Lukáš Korba on 05.07.2022.
//
import XCTest
@testable import secant_testnet
import ComposableArchitecture
class ProfileTests: XCTestCase {
func testSynchronizerStateChanged_AnyButSynced() throws {
let testEnvironment = ProfileEnvironment(
appVersionHandler: .test,
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
walletStorage: .throwing,
zcashSDKEnvironment: .testnet
)
let store = TestStore(
initialState: .placeholder,
reducer: ProfileReducer.default,
environment: testEnvironment
)
store.send(.onAppear) { state in
state.address = "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8"
state.appVersion = "0.0.1"
state.appBuild = "31"
state.sdkVersion = "0.14.0-beta"
}
}
}

View File

@ -0,0 +1,32 @@
//
// ProfileSnapshotTests.swift
// secantTests
//
// Created by Lukáš Korba on 05.07.2022.
//
import XCTest
@testable import secant_testnet
import ComposableArchitecture
import SwiftUI
class ProfileSnapshotTests: XCTestCase {
func testProfileSnapshot_sent() throws {
let testEnvironment = ProfileEnvironment(
appVersionHandler: .test,
mnemonic: .mock,
SDKSynchronizer: TestWrappedSDKSynchronizer(),
walletStorage: .throwing,
zcashSDKEnvironment: .testnet
)
let store = Store(
initialState: .placeholder,
reducer: ProfileReducer.default,
environment: testEnvironment
)
ViewStore(store).send(.onAppear)
addAttachments(ProfileView(store: store))
}
}