diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index 8dc329e..1f5c7ec 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -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 = ""; }; 9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = ""; }; 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsSnapshotTests.swift; sourceTree = ""; }; + 9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = ""; }; + 9E7CB61E2874143800A02233 /* AddressDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDetailsView.swift; sourceTree = ""; }; + 9E7CB61F2874143800A02233 /* AddressDetailsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDetailsStore.swift; sourceTree = ""; }; + 9E7CB6232874246800A02233 /* ProfileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTests.swift; sourceTree = ""; }; + 9E7CB6262874269F00A02233 /* ProfileSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSnapshotTests.swift; sourceTree = ""; }; + 9E7CB6282875AC2D00A02233 /* AppVersionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHandler.swift; sourceTree = ""; }; 9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SDKSynchronizer+SyncStatus.swift"; sourceTree = ""; }; 9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Readable.swift"; sourceTree = ""; }; 9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Chunked.swift"; sourceTree = ""; }; @@ -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 = ""; }; + 9E7CB61B2874140900A02233 /* AddressDetails */ = { + isa = PBXGroup; + children = ( + 9E7CB61F2874143800A02233 /* AddressDetailsStore.swift */, + 9E7CB61E2874143800A02233 /* AddressDetailsView.swift */, + ); + path = AddressDetails; + sourceTree = ""; + }; + 9E7CB6222874245400A02233 /* ProfileTests */ = { + isa = PBXGroup; + children = ( + 9E7CB6232874246800A02233 /* ProfileTests.swift */, + ); + path = ProfileTests; + sourceTree = ""; + }; + 9E7CB6252874267B00A02233 /* ProfileSnapshotTests */ = { + isa = PBXGroup; + children = ( + 9E7CB6262874269F00A02233 /* ProfileSnapshotTests.swift */, + ); + path = ProfileSnapshotTests; + sourceTree = ""; + }; 9E7FE0B6282D1D9800C374E8 /* Resources */ = { isa = PBXGroup; children = ( @@ -908,6 +948,7 @@ 2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */, 9E2F1C832809B606004E65FE /* DebugMenu.swift */, 9E391128283F74590073DD9A /* Zatoshi.swift */, + 9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */, ); path = Utils; sourceTree = ""; @@ -922,6 +963,7 @@ 9E3911472848EEB90073DD9A /* WalletStorage.swift */, 9E3911452848EEB90073DD9A /* ZCashSDKEnvironment.swift */, 9EAB4670285A1C77002904A0 /* DeeplinkHandler.swift */, + 9E7CB6282875AC2D00A02233 /* AppVersionHandler.swift */, ); path = Dependencies; sourceTree = ""; @@ -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 */, diff --git a/secant/Dependencies/AppVersionHandler.swift b/secant/Dependencies/AppVersionHandler.swift new file mode 100644 index 0000000..76f215e --- /dev/null +++ b/secant/Dependencies/AppVersionHandler.swift @@ -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" } + ) +} diff --git a/secant/Dependencies/ZCashSDKEnvironment.swift b/secant/Dependencies/ZCashSDKEnvironment.swift index 323bfc7..82bc17f 100644 --- a/secant/Dependencies/ZCashSDKEnvironment.swift +++ b/secant/Dependencies/ZCashSDKEnvironment.swift @@ -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" ) } diff --git a/secant/Features/AddressDetails/AddressDetailsStore.swift b/secant/Features/AddressDetails/AddressDetailsStore.swift new file mode 100644 index 0000000..c1b2feb --- /dev/null +++ b/secant/Features/AddressDetails/AddressDetailsStore.swift @@ -0,0 +1,80 @@ +// +// AddressDetailsStore.swift +// secant-testnet +// +// Created by Lukáš Korba on 05.07.2022. +// + +import Foundation +import ComposableArchitecture + +typealias AddressDetailsReducer = Reducer +typealias AddressDetailsStore = Store +typealias AddressDetailsViewStore = ViewStore + +// 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 + ) + ) +} diff --git a/secant/Features/AddressDetails/AddressDetailsView.swift b/secant/Features/AddressDetails/AddressDetailsView.swift new file mode 100644 index 0000000..a1efdc1 --- /dev/null +++ b/secant/Features/AddressDetails/AddressDetailsView.swift @@ -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) + } +} diff --git a/secant/Features/App/AppView.swift b/secant/Features/App/AppView.swift index e25c6b4..4391654 100644 --- a/secant/Features/App/AppView.swift +++ b/secant/Features/App/AppView.swift @@ -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 { diff --git a/secant/Features/Home/HomeStore.swift b/secant/Features/Home/HomeStore.swift index 85872aa..f3e5428 100644 --- a/secant/Features/Home/HomeStore.swift +++ b/secant/Features/Home/HomeStore.swift @@ -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 ) } ) diff --git a/secant/Features/Profile/ProfileStore.swift b/secant/Features/Profile/ProfileStore.swift index 75be0a9..b6dcc28 100644 --- a/secant/Features/Profile/ProfileStore.swift +++ b/secant/Features/Profile/ProfileStore.swift @@ -9,74 +9,97 @@ typealias ProfileViewStore = ViewStore 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 { + var bindingForAddressDetails: Binding { 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 { - 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() ) } } diff --git a/secant/Features/Profile/ProfileView.swift b/secant/Features/Profile/ProfileView.swift index 983aaea..076a0ae 100644 --- a/secant/Features/Profile/ProfileView.swift +++ b/secant/Features/Profile/ProfileView.swift @@ -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) } } diff --git a/secant/Features/SendFlow/SendFlowView.swift b/secant/Features/SendFlow/SendFlowView.swift index 85b459f..74bed9f 100644 --- a/secant/Features/SendFlow/SendFlowView.swift +++ b/secant/Features/SendFlow/SendFlowView.swift @@ -50,7 +50,7 @@ struct SendFLowView_Previews: PreviewProvider { ) ) .navigationBarTitleDisplayMode(.inline) - .navigationViewStyle(StackNavigationViewStyle()) + .navigationViewStyle(.stack) } } } diff --git a/secant/Resources/Colors.xcassets/Text/moreInfoText.colorset/Contents.json b/secant/Resources/Colors.xcassets/Text/moreInfoText.colorset/Contents.json new file mode 100644 index 0000000..1f23311 --- /dev/null +++ b/secant/Resources/Colors.xcassets/Text/moreInfoText.colorset/Contents.json @@ -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 + } +} diff --git a/secant/Resources/Generated/XCAssets+Generated.swift b/secant/Resources/Generated/XCAssets+Generated.swift index 0dba425..2f84692 100644 --- a/secant/Resources/Generated/XCAssets+Generated.swift +++ b/secant/Resources/Generated/XCAssets+Generated.swift @@ -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") diff --git a/secant/Utils/QRCodeGenerator.swift b/secant/Utils/QRCodeGenerator.swift new file mode 100644 index 0000000..360a113 --- /dev/null +++ b/secant/Utils/QRCodeGenerator.swift @@ -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 { + Future { 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) + } +} diff --git a/secant/Wrappers/WrappedSDKSynchronizer.swift b/secant/Wrappers/WrappedSDKSynchronizer.swift index 7e7d605..f3391d7 100644 --- a/secant/Wrappers/WrappedSDKSynchronizer.swift +++ b/secant/Wrappers/WrappedSDKSynchronizer.swift @@ -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, diff --git a/secantTests/ProfileTests/ProfileTests.swift b/secantTests/ProfileTests/ProfileTests.swift new file mode 100644 index 0000000..2977e5a --- /dev/null +++ b/secantTests/ProfileTests/ProfileTests.swift @@ -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" + } + } +} diff --git a/secantTests/SnapshotTests/ProfileSnapshotTests/ProfileSnapshotTests.swift b/secantTests/SnapshotTests/ProfileSnapshotTests/ProfileSnapshotTests.swift new file mode 100644 index 0000000..f2cc36a --- /dev/null +++ b/secantTests/SnapshotTests/ProfileSnapshotTests/ProfileSnapshotTests.swift @@ -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)) + } +}