starting to wire app the screens

This commit is contained in:
Francisco Gindre 2020-06-24 23:26:14 -03:00
parent a617a4aefa
commit 3abd221b82
23 changed files with 1178 additions and 62 deletions

View File

@ -3,7 +3,7 @@ use_frameworks!
inhibit_all_warnings!
target 'Zircles' do
pod 'ZcashLightClientKit'
pod 'ZcashLightClientKit', :path => "../hackathon-ZcashLightClientKit"
pod 'KeychainSwift', '~> 19.0.0'
pod 'MnemonicSwift'
pod 'lottie-ios'
@ -25,4 +25,4 @@ post_install do |installer|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
end

View File

@ -69,7 +69,7 @@ DEPENDENCIES:
- KeychainSwift (~> 19.0.0)
- lottie-ios
- MnemonicSwift
- ZcashLightClientKit
- ZcashLightClientKit (from `../hackathon-ZcashLightClientKit`)
SPEC REPOS:
trunk:
@ -97,7 +97,10 @@ SPEC REPOS:
- SwiftNIOTLS
- SwiftNIOTransportServices
- SwiftProtobuf
- ZcashLightClientKit
EXTERNAL SOURCES:
ZcashLightClientKit:
:path: "../hackathon-ZcashLightClientKit"
SPEC CHECKSUMS:
CGRPCZlib: 06247b0687f3a3edbfbfb204d53721c3ba262a34
@ -126,6 +129,6 @@ SPEC CHECKSUMS:
SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932
ZcashLightClientKit: d23fd0513acf929deb5b2a7ef803b03ceda7afb9
PODFILE CHECKSUM: 45c93ff87749c1bbd2a0f94715cb8be9a0c546ae
PODFILE CHECKSUM: aa8517fa897eca8d3c5d128fe3489e0dea05708e
COCOAPODS: 1.9.3

View File

@ -28,6 +28,21 @@
0D5142C624A14D0F00F9AE2E /* CreateNewZircleNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5142C524A14D0F00F9AE2E /* CreateNewZircleNameView.swift */; };
0D5142C824A16F9600F9AE2E /* CreateNewTypeOfZircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5142C724A16F9600F9AE2E /* CreateNewTypeOfZircle.swift */; };
0D64CEA624A3854C0080AA4F /* ZircleOptionSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEA524A3854C0080AA4F /* ZircleOptionSelector.swift */; };
0D64CEA924A3CD1B0080AA4F /* ZirclesEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEA824A3CD1B0080AA4F /* ZirclesEnvironment.swift */; };
0D64CEB424A3CDA90080AA4F /* SeedManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEAA24A3CDA80080AA4F /* SeedManagement.swift */; };
0D64CEB524A3CDA90080AA4F /* String+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEAB24A3CDA80080AA4F /* String+Zcash.swift */; };
0D64CEB624A3CDA90080AA4F /* MnemonicSeedPhraseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEAC24A3CDA80080AA4F /* MnemonicSeedPhraseProvider.swift */; };
0D64CEB724A3CDA90080AA4F /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEAD24A3CDA80080AA4F /* QRCodeGenerator.swift */; };
0D64CEB824A3CDA90080AA4F /* MnemonicSeedPhraseHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEAE24A3CDA80080AA4F /* MnemonicSeedPhraseHandling.swift */; };
0D64CEB924A3CDA90080AA4F /* URL+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEAF24A3CDA80080AA4F /* URL+Zcash.swift */; };
0D64CEBA24A3CDA90080AA4F /* zECC+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEB024A3CDA80080AA4F /* zECC+SwiftUI.swift */; };
0D64CEBB24A3CDA90080AA4F /* CameraAccessHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEB124A3CDA90080AA4F /* CameraAccessHelper.swift */; };
0D64CEBC24A3CDA90080AA4F /* BalanceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEB224A3CDA90080AA4F /* BalanceUtils.swift */; };
0D64CEBD24A3CDA90080AA4F /* SimpleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEB324A3CDA90080AA4F /* SimpleLogger.swift */; };
0D64CEBF24A3D3A30080AA4F /* CombineSynchronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEBE24A3D3A30080AA4F /* CombineSynchronizer.swift */; };
0D64CEC124A3F2580080AA4F /* FirstScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEC024A3F2580080AA4F /* FirstScreen.swift */; };
0D64CEC324A4137D0080AA4F /* ZircleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEC224A4137D0080AA4F /* ZircleData.swift */; };
0D64CEC524A41AF30080AA4F /* AppDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D64CEC424A41AF30080AA4F /* AppDetails.swift */; };
0D6A22C0249A9C3000B4E946 /* Colors+Zircles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6A22BF249A9C3000B4E946 /* Colors+Zircles.swift */; };
0D6A22C5249AB1FC00B4E946 /* ZcashSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6A22C4249AB1FC00B4E946 /* ZcashSymbol.swift */; };
0D6A22C7249AB36100B4E946 /* ZcashButtonBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6A22C6249AB36100B4E946 /* ZcashButtonBackground.swift */; };
@ -83,6 +98,21 @@
0D5142C524A14D0F00F9AE2E /* CreateNewZircleNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewZircleNameView.swift; sourceTree = "<group>"; };
0D5142C724A16F9600F9AE2E /* CreateNewTypeOfZircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewTypeOfZircle.swift; sourceTree = "<group>"; };
0D64CEA524A3854C0080AA4F /* ZircleOptionSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZircleOptionSelector.swift; sourceTree = "<group>"; };
0D64CEA824A3CD1B0080AA4F /* ZirclesEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZirclesEnvironment.swift; sourceTree = "<group>"; };
0D64CEAA24A3CDA80080AA4F /* SeedManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedManagement.swift; sourceTree = "<group>"; };
0D64CEAB24A3CDA80080AA4F /* String+Zcash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Zcash.swift"; sourceTree = "<group>"; };
0D64CEAC24A3CDA80080AA4F /* MnemonicSeedPhraseProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MnemonicSeedPhraseProvider.swift; sourceTree = "<group>"; };
0D64CEAD24A3CDA80080AA4F /* QRCodeGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
0D64CEAE24A3CDA80080AA4F /* MnemonicSeedPhraseHandling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MnemonicSeedPhraseHandling.swift; sourceTree = "<group>"; };
0D64CEAF24A3CDA80080AA4F /* URL+Zcash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Zcash.swift"; sourceTree = "<group>"; };
0D64CEB024A3CDA80080AA4F /* zECC+SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "zECC+SwiftUI.swift"; sourceTree = "<group>"; };
0D64CEB124A3CDA90080AA4F /* CameraAccessHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraAccessHelper.swift; sourceTree = "<group>"; };
0D64CEB224A3CDA90080AA4F /* BalanceUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceUtils.swift; sourceTree = "<group>"; };
0D64CEB324A3CDA90080AA4F /* SimpleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleLogger.swift; sourceTree = "<group>"; };
0D64CEBE24A3D3A30080AA4F /* CombineSynchronizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombineSynchronizer.swift; sourceTree = "<group>"; };
0D64CEC024A3F2580080AA4F /* FirstScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstScreen.swift; sourceTree = "<group>"; };
0D64CEC224A4137D0080AA4F /* ZircleData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZircleData.swift; sourceTree = "<group>"; };
0D64CEC424A41AF30080AA4F /* AppDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetails.swift; sourceTree = "<group>"; };
0D6A22BF249A9C3000B4E946 /* Colors+Zircles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+Zircles.swift"; sourceTree = "<group>"; };
0D6A22C4249AB1FC00B4E946 /* ZcashSymbol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZcashSymbol.swift; sourceTree = "<group>"; };
0D6A22C6249AB36100B4E946 /* ZcashButtonBackground.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZcashButtonBackground.swift; sourceTree = "<group>"; };
@ -142,8 +172,20 @@
0D11D3D5249D2E0400223146 /* Utils */ = {
isa = PBXGroup;
children = (
0D64CEB224A3CDA90080AA4F /* BalanceUtils.swift */,
0D64CEB124A3CDA90080AA4F /* CameraAccessHelper.swift */,
0D64CEAE24A3CDA80080AA4F /* MnemonicSeedPhraseHandling.swift */,
0D64CEAC24A3CDA80080AA4F /* MnemonicSeedPhraseProvider.swift */,
0D64CEAD24A3CDA80080AA4F /* QRCodeGenerator.swift */,
0D64CEAA24A3CDA80080AA4F /* SeedManagement.swift */,
0D64CEB324A3CDA90080AA4F /* SimpleLogger.swift */,
0D64CEAB24A3CDA80080AA4F /* String+Zcash.swift */,
0D64CEAF24A3CDA80080AA4F /* URL+Zcash.swift */,
0D64CEB024A3CDA80080AA4F /* zECC+SwiftUI.swift */,
0D11D3D8249D51FC00223146 /* ZircleTextField_Preview.swift */,
0D6A22CA249AB61200B4E946 /* Glow_Preview.swift */,
0D64CEC024A3F2580080AA4F /* FirstScreen.swift */,
0D64CEC224A4137D0080AA4F /* ZircleData.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -173,6 +215,8 @@
0D1366AA24991A6000F0EB54 /* Zircles */ = {
isa = PBXGroup;
children = (
0D64CEBE24A3D3A30080AA4F /* CombineSynchronizer.swift */,
0D64CEA724A3CD040080AA4F /* Environment */,
0D5142C424A1323900F9AE2E /* Views */,
0D5142C224A0F0B800F9AE2E /* Zboto.otf */,
0D11D3D5249D2E0400223146 /* Utils */,
@ -223,10 +267,19 @@
0D5142C724A16F9600F9AE2E /* CreateNewTypeOfZircle.swift */,
0DEE59A724A24B7300447C15 /* WelcomeView.swift */,
0DEE59A924A2B90F00447C15 /* CreateNewZircleDescription.swift */,
0D64CEC424A41AF30080AA4F /* AppDetails.swift */,
);
path = Views;
sourceTree = "<group>";
};
0D64CEA724A3CD040080AA4F /* Environment */ = {
isa = PBXGroup;
children = (
0D64CEA824A3CD1B0080AA4F /* ZirclesEnvironment.swift */,
);
path = Environment;
sourceTree = "<group>";
};
0D6A22C1249AB12200B4E946 /* Components */ = {
isa = PBXGroup;
children = (
@ -506,23 +559,38 @@
buildActionMask = 2147483647;
files = (
0D6A22C9249AB3CA00B4E946 /* ZcashButton.swift in Sources */,
0D64CEB924A3CDA90080AA4F /* URL+Zcash.swift in Sources */,
0D64CEC524A41AF30080AA4F /* AppDetails.swift in Sources */,
0D6A22C0249A9C3000B4E946 /* Colors+Zircles.swift in Sources */,
0D11D3DD249D81B900223146 /* Pie.swift in Sources */,
0D64CEB824A3CDA90080AA4F /* MnemonicSeedPhraseHandling.swift in Sources */,
0D6A22C5249AB1FC00B4E946 /* ZcashSymbol.swift in Sources */,
0D1366AC24991A6000F0EB54 /* AppDelegate.swift in Sources */,
0D11D3CE249C294E00223146 /* NeumorphicButtons.swift in Sources */,
0D6A22C7249AB36100B4E946 /* ZcashButtonBackground.swift in Sources */,
0D64CEB624A3CDA90080AA4F /* MnemonicSeedPhraseProvider.swift in Sources */,
0D11D3D4249D05D800223146 /* Wedge.swift in Sources */,
0D1366AE24991A6000F0EB54 /* SceneDelegate.swift in Sources */,
0D11D3D0249C3AE400223146 /* Card.swift in Sources */,
0D64CEB724A3CDA90080AA4F /* QRCodeGenerator.swift in Sources */,
0D64CEBF24A3D3A30080AA4F /* CombineSynchronizer.swift in Sources */,
0D6A22CB249AB61200B4E946 /* Glow_Preview.swift in Sources */,
0D5142C624A14D0F00F9AE2E /* CreateNewZircleNameView.swift in Sources */,
0DEE59AA24A2B90F00447C15 /* CreateNewZircleDescription.swift in Sources */,
0D11D3D7249D2F0B00223146 /* ZircleProgress.swift in Sources */,
0D11D3D9249D51FC00223146 /* ZircleTextField_Preview.swift in Sources */,
0D64CEC124A3F2580080AA4F /* FirstScreen.swift in Sources */,
0D64CEBA24A3CDA90080AA4F /* zECC+SwiftUI.swift in Sources */,
0D11D3D2249CE6C800223146 /* GlowEffect.swift in Sources */,
0D64CEB524A3CDA90080AA4F /* String+Zcash.swift in Sources */,
0D11D3DB249D5F1600223146 /* ProgressBar.swift in Sources */,
0D64CEBD24A3CDA90080AA4F /* SimpleLogger.swift in Sources */,
0D64CEBC24A3CDA90080AA4F /* BalanceUtils.swift in Sources */,
0D5142C824A16F9600F9AE2E /* CreateNewTypeOfZircle.swift in Sources */,
0D64CEBB24A3CDA90080AA4F /* CameraAccessHelper.swift in Sources */,
0D64CEB424A3CDA90080AA4F /* SeedManagement.swift in Sources */,
0D64CEC324A4137D0080AA4F /* ZircleData.swift in Sources */,
0D64CEA924A3CD1B0080AA4F /* ZirclesEnvironment.swift in Sources */,
0D1366B024991A6000F0EB54 /* SplashScreen.swift in Sources */,
0DEE59A824A24B7300447C15 /* WelcomeView.swift in Sources */,
0D64CEA624A3854C0080AA4F /* ZircleOptionSelector.swift in Sources */,

View File

@ -7,7 +7,7 @@
//
import UIKit
let logger = SimpleLogger(logLevel: .debug)
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

View File

@ -0,0 +1,161 @@
//
// CombineSynchronizer.swift
// wallet
//
// Created by Francisco Gindre on 1/27/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import Combine
import ZcashLightClientKit
class CombineSynchronizer {
var initializer: Initializer {
synchronizer.initializer
}
private var synchronizer: SDKSynchronizer
var status: CurrentValueSubject<Status, Never>
var progress: CurrentValueSubject<Float,Never>
var syncBlockHeight: CurrentValueSubject<BlockHeight,Never>
var minedTransaction = PassthroughSubject<PendingTransactionEntity,Never>()
var balance: CurrentValueSubject<Double,Never>
var verifiedBalance: CurrentValueSubject<Double,Never>
var cancellables = [AnyCancellable]()
var error = PassthroughSubject<Error, Never>()
var receivedTransactions: Future<[ConfirmedTransactionEntity],Never> {
Future<[ConfirmedTransactionEntity], Never>() {
promise in
DispatchQueue.global().async {
[weak self] in
guard let self = self else {
promise(.success([]))
return
}
promise(.success(self.synchronizer.receivedTransactions))
}
}
}
var sentTransactions: Future<[ConfirmedTransactionEntity], Never> {
Future<[ConfirmedTransactionEntity], Never>() {
promise in
DispatchQueue.global().async {
[weak self] in
guard let self = self else {
promise(.success([]))
return
}
promise(.success(self.synchronizer.sentTransactions))
}
}
}
var pendingTransactions: Future<[PendingTransactionEntity], Never> {
Future<[PendingTransactionEntity], Never>(){
[weak self ] promise in
guard let self = self else {
promise(.success([]))
return
}
DispatchQueue.global().async {
promise(.success(self.synchronizer.pendingTransactions))
}
}
}
init(initializer: Initializer) throws {
self.synchronizer = try SDKSynchronizer(initializer: initializer)
self.status = CurrentValueSubject(.disconnected)
self.progress = CurrentValueSubject(0)
self.balance = CurrentValueSubject(0)
self.verifiedBalance = CurrentValueSubject(0)
self.syncBlockHeight = CurrentValueSubject(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
NotificationCenter.default.publisher(for: .synchronizerSynced).sink(receiveValue: { _ in
self.balance.send(initializer.getBalance().asHumanReadableZecBalance())
self.verifiedBalance.send(initializer.getVerifiedBalance().asHumanReadableZecBalance())
}).store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerStarted).sink { _ in
self.status.send(.syncing)
}.store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerProgressUpdated).receive(on: DispatchQueue.main).sink(receiveValue: { (progressNotification) in
guard let newProgress = progressNotification.userInfo?[SDKSynchronizer.NotificationKeys.progress] as? Float else { return }
self.progress.send(newProgress)
guard let blockHeight = progressNotification.userInfo?[SDKSynchronizer.NotificationKeys.blockHeight] as? BlockHeight else { return }
self.syncBlockHeight.send(blockHeight)
}).store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerMinedTransaction).sink(receiveValue: {minedNotification in
guard let minedTx = minedNotification.userInfo?[SDKSynchronizer.NotificationKeys.minedTransaction] as? PendingTransactionEntity else { return }
self.minedTransaction.send(minedTx)
}).store(in: &cancellables)
NotificationCenter.default.publisher(for: .synchronizerFailed).sink { (notification) in
guard let error = notification.userInfo?[SDKSynchronizer.NotificationKeys.error] as? Error else {
self.error.send(ZirclesEnvironment.WalletError.genericError(message: "An error ocurred, but we can't figure out what it is. Please check device logs for more details")
)
return
}
self.error.send(error)
}.store(in: &cancellables)
}
func start(retry: Bool = false){
do {
if retry {
stop()
}
try synchronizer.start(retry: retry)
} catch {
logger.error("error starting \(error)")
}
}
func stop() {
do {
try synchronizer.stop()
} catch {
logger.error("error stopping \(error)")
}
}
func cancel(pendingTransaction: PendingTransactionEntity) -> Bool {
synchronizer.cancelSpend(transaction: pendingTransaction)
}
deinit {
for c in cancellables {
c.cancel()
}
}
func send(with spendingKey: String, zatoshi: Int64, to recipientAddress: String, memo: String?,from account: Int) -> Future<PendingTransactionEntity,Error> {
Future<PendingTransactionEntity, Error>() {
promise in
self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: recipientAddress, memo: memo, from: account) { (result) in
switch result {
case .failure(let error):
promise(.failure(error))
case .success(let pendingTx):
promise(.success(pendingTx))
}
}
}
}
}

View File

@ -0,0 +1,221 @@
//
// ZirclesEnvironment.swift
// Zircles
//
// Created by Francisco Gindre on 6/24/20.
// Copyright © 2020 Electric Coin Company. All rights reserved.
//
import Foundation
import SwiftUI
import ZcashLightClientKit
import Combine
enum WalletState {
case initalized
case uninitialized
case syncing
case synced
}
protocol AppEnvironment {
}
final class ZirclesEnvironment: ObservableObject {
enum WalletError: Error {
case createFailed
case initializationFailed(message: String)
case genericError(message: String)
case connectionFailed(message: String)
case maxRetriesReached(attempts: Int)
}
static let genericErrorMessage = "An error ocurred, please check your device logs"
static var shared: ZirclesEnvironment = try! ZirclesEnvironment() // app can't live without this existing.
@Published var state: WalletState
let endpoint = LightWalletEndpoint(address: ZcashSDK.isMainnet ? "lightwalletd.z.cash" : "lightwalletd.testnet.z.cash", port: 9067, secure: true)
var dataDbURL: URL
var cacheDbURL: URL
var pendingDbURL: URL
var outputParamsURL: URL
var spendParamsURL: URL
var initializer: Initializer {
synchronizer.initializer
}
var synchronizer: CombineSynchronizer
var cancellables = [AnyCancellable]()
static func getInitialState() -> WalletState {
guard let keys = SeedManager.default.getKeys(), keys.count > 0 else {
return .uninitialized
}
return .initalized
}
static func isInitialized() -> Bool {
switch getInitialState() {
case .uninitialized:
return false
default:
return true
}
}
private init() throws {
self.dataDbURL = try URL.dataDbURL()
self.cacheDbURL = try URL.cacheDbURL()
self.pendingDbURL = try URL.pendingDbURL()
self.outputParamsURL = try URL.outputParamsURL()
self.spendParamsURL = try URL.spendParamsURL()
self.state = Self.getInitialState()
let initializer = Initializer(
cacheDbURL: self.cacheDbURL,
dataDbURL: self.dataDbURL,
pendingDbURL: self.pendingDbURL,
endpoint: endpoint,
spendParamsURL: self.spendParamsURL,
outputParamsURL: self.outputParamsURL,
loggerProxy: logger)
self.synchronizer = try CombineSynchronizer(initializer: initializer)
cancellables.append(
self.synchronizer.status.map({
status -> WalletState in
switch status {
case .synced:
return WalletState.synced
case .syncing:
return WalletState.syncing
default:
return Self.getInitialState()
}
}).sink(receiveValue: { status in
self.state = status
})
)
}
func createNewWallet() throws {
guard let randomPhrase = MnemonicSeedProvider.default.randomMnemonic(),
let randomSeed = MnemonicSeedProvider.default.toSeed(mnemonic: randomPhrase) else {
throw WalletError.createFailed
}
let birthday = WalletBirthday.birthday(with: BlockHeight.max)
try SeedManager.default.importSeed(randomSeed)
try SeedManager.default.importBirthday(birthday.height)
try SeedManager.default.importPhrase(bip39: randomPhrase)
try self.initialize()
}
func initialize() throws {
if let keys = try self.initializer.initialize(seedProvider: SeedManager.default, walletBirthdayHeight: try SeedManager.default.exportBirthday()) {
SeedManager.default.saveKeys(keys)
}
self.synchronizer.start()
}
/**
only for internal use
*/
func nuke(abortApplication: Bool = false) {
self.synchronizer.stop()
SeedManager.default.nukeWallet()
do {
try FileManager.default.removeItem(at: self.dataDbURL)
} catch {
logger.error("could not nuke wallet: \(error)")
}
do {
try FileManager.default.removeItem(at: self.cacheDbURL)
} catch {
logger.error("could not nuke wallet: \(error)")
}
do {
try FileManager.default.removeItem(at: self.pendingDbURL)
} catch {
logger.error("could not nuke wallet: \(error)")
}
if abortApplication {
abort()
}
}
static func mapError(error: Error) -> ZirclesEnvironment.WalletError {
if let rustError = error as? RustWeldingError {
switch rustError {
case .genericError(let message):
return ZirclesEnvironment.WalletError.genericError(message: message)
case .dataDbInitFailed(let message):
return ZirclesEnvironment.WalletError.genericError(message: message)
case .dataDbNotEmpty:
return ZirclesEnvironment.WalletError.genericError(message: "attempt to initialize a db that was not empty")
case .saplingSpendParametersNotFound:
return ZirclesEnvironment.WalletError.createFailed
case .malformedStringInput:
return ZirclesEnvironment.WalletError.genericError(message: "Malformed address or key detected")
default:
return WalletError.genericError(message: "\(rustError)")
}
} else if let synchronizerError = error as? SynchronizerError {
switch synchronizerError {
case .generalError(let message):
return ZirclesEnvironment.WalletError.genericError(message: message)
case .initFailed(let message):
return WalletError.initializationFailed(message: "Synchronizer failed to initialize: \(message)")
case .syncFailed:
return WalletError.genericError(message: "Synchronizing failed")
case .connectionFailed(let message):
return WalletError.connectionFailed(message: message)
case .maxRetryAttemptsReached(attempts: let attempts):
return WalletError.maxRetriesReached(attempts: attempts)
case .connectionError(_, let message):
return WalletError.connectionFailed(message: message)
}
}
return ZirclesEnvironment.WalletError.genericError(message: Self.genericErrorMessage)
}
deinit {
cancellables.forEach {
c in
c.cancel()
}
}
}
extension ZirclesEnvironment {
static var appBuild: String? {
Bundle.main.infoDictionary?["CFBundleVersion"] as? String
}
static var appVersion: String? {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
}
func isValidAddress(_ address: String) -> Bool {
self.initializer.isValidShieldedAddress(address) || self.initializer.isValidTransparentAddress(address)
}
func sufficientFundsToSend(amount: Double) -> Bool {
return sufficientFunds(availableBalance: self.initializer.getVerifiedBalance(), zatoshiToSend: amount.toZatoshi())
}
private func sufficientFunds(availableBalance: Int64, zatoshiToSend: Int64) -> Bool {
availableBalance - zatoshiToSend - Int64(ZcashSDK.MINERS_FEE_ZATOSHI) >= 0
}
static var minerFee: Double {
Int64(ZcashSDK.MINERS_FEE_ZATOSHI).asHumanReadableZecBalance()
}
}

View File

@ -20,7 +20,14 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Create the SwiftUI view that provides the window contents.
let contentView = SplashScreen()
let isInitialized = ZirclesEnvironment.isInitialized()
let contentView = FirstScreen() {
if isInitialized {
SplashScreen()
} else {
WelcomeView()
}
}
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
@ -30,7 +37,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window.makeKeyAndVisible()
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.

View File

@ -0,0 +1,61 @@
//
// BalanceUtils.swift
// wallet
//
// Created by Francisco Gindre on 1/2/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import ZcashLightClientKit
extension Int64 {
func asHumanReadableZecBalance() -> Double {
var decimal = Decimal(self) / Decimal(ZcashSDK.ZATOSHI_PER_ZEC)
var rounded = Decimal()
NSDecimalRound(&rounded, &decimal, 6, .bankers)
return (rounded as NSDecimalNumber).doubleValue
}
}
extension Double {
func toZatoshi() -> Int64 {
var decimal = Decimal(self) * Decimal(ZcashSDK.ZATOSHI_PER_ZEC)
var rounded = Decimal()
NSDecimalRound(&rounded, &decimal, 6, .bankers)
return (rounded as NSDecimalNumber).int64Value
}
func toZecAmount() -> String {
NumberFormatter.zecAmountFormatter.string(from: NSNumber(value: self)) ?? "\(self)"
}
}
extension NumberFormatter {
static var zecAmountFormatter: NumberFormatter {
let fmt = NumberFormatter()
fmt.alwaysShowsDecimalSeparator = false
fmt.allowsFloats = true
fmt.maximumFractionDigits = 8
fmt.minimumFractionDigits = 0
fmt.minimumIntegerDigits = 1
return fmt
}
static var zeroBalanceFormatter: NumberFormatter {
let fmt = NumberFormatter()
fmt.alwaysShowsDecimalSeparator = false
fmt.allowsFloats = true
fmt.maximumFractionDigits = 0
fmt.minimumFractionDigits = 0
fmt.minimumIntegerDigits = 1
return fmt
}
}

View File

@ -0,0 +1,38 @@
//
// CameraAccessHelper.swift
// wallet
//
// Created by Francisco Gindre on 2/5/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import AVFoundation
class CameraAccessHelper {
enum Status {
case authorized
case unauthorized
case unavailable
case undetermined
}
static var authorizationStatus: CameraAccessHelper.Status {
let cameraMediaType = AVMediaType.video
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: cameraMediaType)
switch cameraAuthorizationStatus {
case .authorized:
return Status.authorized
case .denied:
return Status.unauthorized
case .restricted:
return .unavailable
case .notDetermined:
return Status.undetermined
@unknown default:
return .unavailable
}
}
}

View File

@ -0,0 +1,32 @@
//
// FirstScreen.swift
// Zircles
//
// Created by Francisco Gindre on 6/24/20.
// Copyright © 2020 Electric Coin Company. All rights reserved.
//
import SwiftUI
struct FirstScreen<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
NavigationView {
content
}
}
}
struct FirstScreen_Previews: PreviewProvider {
static var previews: some View {
FirstScreen() {
ZStack {
Color.background
Text("hello")
}.navigationBarTitle("Welcome")
}
}
}

View File

@ -0,0 +1,38 @@
//
// MnemonicSeedPhraseHandling.swift
// wallet
//
// Created by Francisco Gindre on 2/28/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
enum MnemonicError: Error {
case invalidSeed
}
protocol MnemonicSeedPhraseHandling {
/**
random 24 words mnemonic phrase
*/
func randomMnemonic() -> String?
/**
random 24 words mnemonic phrase as array of words
*/
func randomMnemonicWords() -> [String]?
/**
generate deterministic seed from mnemonic phrase
*/
func toSeed(mnemonic: String) -> [UInt8]?
/**
get this mnemonic
*/
func asWords(mnemonic: String) -> [String]?
/**
validates whether the given mnemonic is correct
*/
func isValid(mnemonic: String) -> Bool
}

View File

@ -0,0 +1,37 @@
//
// MnemonicSeedPhraseProvider.swift
// wallet
//
// Created by Francisco Gindre on 2/28/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import MnemonicSwift
class MnemonicSeedProvider: MnemonicSeedPhraseHandling {
static let `default` = MnemonicSeedProvider()
private init(){}
func randomMnemonic() -> String? {
Mnemonic.generateMnemonic(strength: 256)
}
func randomMnemonicWords() -> [String]? {
randomMnemonic()?.split(separator: " ").map({ String($0) })
}
func toSeed(mnemonic: String) -> [UInt8]? {
guard let data = Mnemonic.deterministicSeedBytes(from: mnemonic) else { return nil }
return [UInt8](data)
}
func asWords(mnemonic: String) -> [String]? {
mnemonic.split(separator: " ").map({ String($0) })
}
func isValid(mnemonic: String) -> Bool {
Mnemonic.validate(mnemonic: mnemonic)
}
}

View File

@ -0,0 +1,51 @@
//
// QRCodeGenerator.swift
// wallet
//
// Created by Francisco Gindre on 2/3/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import Combine
import CoreImage
import CoreImage.CIFilterBuiltins
import SwiftUI
class 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

@ -0,0 +1,115 @@
//
// SeedManagement.swift
// wallet
//
// Created by Francisco Gindre on 1/23/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import KeychainSwift
import ZcashLightClientKit
final class SeedManager {
enum SeedManagerError: Error {
case alreadyImported
case uninitializedWallet
}
static var `default`: SeedManager = SeedManager()
private static let zECCWalletKeys = "zECCWalletKeys"
private static let zECCWalletSeedKey = "zEECWalletSeedKey"
private static let zECCWalletBirthday = "zECCWalletBirthday"
private static let zECCWalletPhrase = "zECCWalletPhrase"
private let keychain = KeychainSwift()
func importBirthday(_ height: BlockHeight) throws {
guard keychain.get(Self.zECCWalletBirthday) == nil else {
throw SeedManagerError.alreadyImported
}
keychain.set(String(height), forKey: Self.zECCWalletBirthday)
}
func exportBirthday() throws -> BlockHeight {
guard let birthday = keychain.get(Self.zECCWalletBirthday),
let value = BlockHeight(birthday) else {
throw SeedManagerError.uninitializedWallet
}
return value
}
func importSeed(_ seed: [UInt8]) throws {
guard keychain.get(Self.zECCWalletSeedKey) == nil else { throw SeedManagerError.alreadyImported }
keychain.set(Data(seed), forKey: Self.zECCWalletSeedKey)
}
func exportSeed() throws -> [UInt8] {
guard let seedData = keychain.getData(Self.zECCWalletSeedKey) else { throw SeedManagerError.uninitializedWallet }
return [UInt8](seedData)
}
func importPhrase(bip39 phrase: String) throws {
guard keychain.get(Self.zECCWalletPhrase) == nil else { throw SeedManagerError.alreadyImported }
keychain.set(phrase, forKey: Self.zECCWalletPhrase)
}
func exportPhrase() throws -> String {
guard let seed = keychain.get(Self.zECCWalletPhrase) else { throw SeedManagerError.uninitializedWallet }
return seed
}
func saveKeys(_ keys: [String]) {
keychain.set(keys.joined(separator: ";"), forKey: Self.zECCWalletKeys)
}
func getKeys() -> [String]? {
keychain.get(Self.zECCWalletKeys)?.split(separator: ";").map{String($0)}
}
/**
*/
func nukePhrase() {
keychain.delete(Self.zECCWalletPhrase)
}
/**
Use carefully: Deletes the keys from the keychain
*/
func nukeKeys() {
keychain.delete(Self.zECCWalletKeys)
}
/**
Use carefully: Deletes the seed from the keychain.
*/
func nukeSeed() {
keychain.delete(Self.zECCWalletSeedKey)
}
/**
Use carefully: deletes the wallet birthday from the keychain
*/
func nukeBirthday() {
keychain.delete(Self.zECCWalletBirthday)
}
/**
There's no fate but what we make for ourselves - Sarah Connor
*/
func nukeWallet() {
nukeKeys()
nukeSeed()
nukePhrase()
nukeBirthday()
}
}
extension SeedManager: SeedProvider {
func seed() -> [UInt8] {
(try? exportSeed()) ?? []
}
}

View File

@ -0,0 +1,74 @@
//
// SimpleLogger.swift
// wallet
//
// Created by Francisco Gindre on 3/9/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import ZcashLightClientKit
import os
class SimpleLogger: ZcashLightClientKit.Logger {
enum LogLevel: Int {
case debug
case error
case warning
case event
case info
}
enum LoggerType {
case osLog
case printerLog
}
var level: LogLevel
var loggerType: LoggerType
init(logLevel: LogLevel, type: LoggerType = .osLog) {
self.level = logLevel
self.loggerType = type
}
private static let subsystem = Bundle.main.bundleIdentifier!
static let oslog = OSLog(subsystem: subsystem, category: "logs")
func debug(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
guard level.rawValue == LogLevel.debug.rawValue else { return }
log(level: "DEBUG 🐞", message: message, file: file, function: function, line: line)
}
func error(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
guard level.rawValue <= LogLevel.error.rawValue else { return }
log(level: "ERROR 💥", message: message, file: file, function: function, line: line)
}
func warn(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
guard level.rawValue <= LogLevel.warning.rawValue else { return }
log(level: "WARNING ⚠️", message: message, file: file, function: function, line: line)
}
func event(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
guard level.rawValue <= LogLevel.event.rawValue else { return }
log(level: "EVENT ⏱", message: message, file: file, function: function, line: line)
}
func info(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
guard level.rawValue <= LogLevel.info.rawValue else { return }
log(level: "INFO ", message: message, file: file, function: function, line: line)
}
private func log(level: String, message: String, file: String, function: String, line: Int) {
let fileName = (file as NSString).lastPathComponent
switch loggerType {
case .printerLog:
print("[\(level)] \(fileName) - \(function) - line: \(line) -> \(message)")
default:
// os_log("[%@] %@ - %@ - Line: %d -> %@", log: Self.oslog, type: .debug, level, fileName, function, line, message)
os_log("[%{public}@] %{public}@ - %{public}@ - Line: %{public}d -> %{public}@", level, fileName, function, line, message)
}
}
}

View File

@ -0,0 +1,24 @@
//
// String+Zcash.swift
// wallet
//
// Created by Francisco Gindre on 1/22/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
extension String {
var isValidShieldedAddress: Bool {
ZirclesEnvironment.shared.initializer.isValidShieldedAddress(self)
}
var shortZaddress: String? {
guard isValidShieldedAddress else { return nil }
return String(self[self.startIndex ..< self.index(self.startIndex, offsetBy: 8)])
+ "..."
+ String(self[self.index(self.endIndex, offsetBy: -8) ..< self.endIndex])
}
}

View File

@ -0,0 +1,38 @@
//
// URL+Zcash.swift
// wallet
//
// Created by Francisco Gindre on 1/24/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import ZcashLightClientKit
extension URL {
static func documentsDirectory() throws -> URL {
try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
}
static func cacheDbURL() throws -> URL {
try documentsDirectory().appendingPathComponent(ZcashSDK.DEFAULT_DB_NAME_PREFIX+ZcashSDK.DEFAULT_CACHES_DB_NAME, isDirectory: false)
}
static func dataDbURL() throws -> URL {
try documentsDirectory().appendingPathComponent(ZcashSDK.DEFAULT_DB_NAME_PREFIX+ZcashSDK.DEFAULT_DATA_DB_NAME, isDirectory: false)
}
static func pendingDbURL() throws -> URL {
try documentsDirectory().appendingPathComponent(ZcashSDK.DEFAULT_DB_NAME_PREFIX+ZcashSDK.DEFAULT_PENDING_DB_NAME)
}
static func spendParamsURL() throws -> URL {
Bundle.main.url(forResource: "sapling-spend", withExtension: ".params")!
}
static func outputParamsURL() throws -> URL {
Bundle.main.url(forResource: "sapling-output", withExtension: ".params")!
}
}

View File

@ -0,0 +1,24 @@
//
// ZircleData.swift
// Zircles
//
// Created by Francisco Gindre on 6/24/20.
// Copyright © 2020 Electric Coin Company. All rights reserved.
//
import Foundation
class ZircleDataStorage {
static let usernameKey = "zircleusername"
static var `default`: ZircleDataStorage = ZircleDataStorage()
private init() {}
func saveUsername(_ name: String) {
UserDefaults.standard.setValue(name, forKey: Self.usernameKey)
}
var username: String {
UserDefaults.standard.string(forKey: Self.usernameKey) ?? ""
}
}

View File

@ -0,0 +1,16 @@
//
// zECC+SwiftUI.swift
// wallet
//
// Created by Francisco Gindre on 2/12/20.
// Copyright © 2020 Francisco Gindre. All rights reserved.
//
import Foundation
import SwiftUI
extension ZcashButton {
static func nukeButton() -> ZcashButton {
ZcashButton(color: Color.red, fill: Color.clear, text: "NUKE WALLET")
}
}

View File

@ -0,0 +1,84 @@
//
// AppDetails.swift
// Zircles
//
// Created by Francisco Gindre on 6/24/20.
// Copyright © 2020 Electric Coin Company. All rights reserved.
//
import SwiftUI
struct AppDetails: View {
@State var showNukeAlert = false
var body: some View {
ZStack {
Color.background.edgesIgnoringSafeArea(.all)
VStack {
VStack(alignment: .leading, spacing: 3) {
Text("Username")
.foregroundColor(.textLightGray)
.fontWeight(.heavy)
.font(.footnote)
.frame(alignment: .leading)
Card(isOn: .constant(true),cornerRadius: 5,padding: 8) {
Text(ZircleDataStorage.default.username)
.foregroundColor(Color.textDarkGray)
.font(.system(size: 14, weight: .heavy, design: .default))
}
}.padding(.all, 0)
VStack(alignment: .leading, spacing: 3) {
Text("App Version")
.foregroundColor(.textLightGray)
.fontWeight(.heavy)
.font(.footnote)
.frame(alignment: .leading)
Card(isOn: .constant(true),cornerRadius: 5,padding: 8) {
Text("Zircles v\(ZirclesEnvironment.appVersion ?? "Unknown") - Build: \(ZirclesEnvironment.appBuild ?? "Unknown")")
.foregroundColor(Color.textDarkGray)
.font(.system(size: 14, weight: .heavy, design: .default))
}
}.padding(.all, 0)
Button(action: {
self.showNukeAlert = true
}) {
Text("NUKE WALLET")
.foregroundColor(.red)
.zcashButtonBackground(shape: .roundedCorners(fillStyle: .outline(color: .red, lineWidth: 2)))
.frame(height: 48)
}.alert(isPresented: $showNukeAlert) {
Alert(title: Text("Delete Wallet?"),
message: Text("You are about to")+Text(" nuke your wallet. ").foregroundColor(.red) + Text("Are you sure you want to proceed?"),
primaryButton: .default(
Text("I'm not sure")
,action: { self.showNukeAlert = false}
),
secondaryButton: .destructive(
Text("NUKE WALLET!"),
action: {
ZirclesEnvironment.shared.nuke(abortApplication: true)
}
)
)
}
}
}.navigationBarTitle(Text("Backstage"))
.navigationBarItems(trailing: Button(action:{
}){
Text("close")
})
}
}
struct AppDetails_Previews: PreviewProvider {
static var previews: some View {
AppDetails()
}
}

View File

@ -80,7 +80,7 @@ struct CreateNewZircleDescription: View {
.frame(alignment: .leading)
Card(isOn: .constant(true),cornerRadius: 5,padding: 8) {
TextField("some title text", text: .constant("Hackathon Happy Hour"))
TextField("some title text", text: .constant("3 ZEC"))
.foregroundColor(Color.textDarkGray)
.font(.system(size: 14, weight: .heavy, design: .default))

View File

@ -11,53 +11,50 @@ import SwiftUI
struct SplashScreen: View {
var body: some View {
NavigationView {
ZStack {
Color.background
.edgesIgnoringSafeArea(.all)
VStack {
Toggle(isOn: .constant(false)) {
Text("No Proyects Yet")
.foregroundColor(Color.textLightGray)
.fontWeight(.heavy)
ZStack {
Color.background
.edgesIgnoringSafeArea(.all)
VStack {
Toggle(isOn: .constant(false)) {
Text("No Proyects Yet")
.foregroundColor(Color.textLightGray)
.fontWeight(.heavy)
.contentShape(RoundedRectangle(cornerRadius: 5))
.scaledToFill()
.frame(width: 250)
}
.contentShape(RoundedRectangle(cornerRadius: 5))
.toggleStyle(SimpleToggleStyle(shape: RoundedRectangle(cornerRadius: 5), padding: 8))
Spacer()
FancyLogo()
.frame(width: 200, height: 200)
Spacer()
VStack(spacing: 16) {
Text("Join a Zircle")
.frame(width: 250)
}
.contentShape(RoundedRectangle(cornerRadius: 5))
.toggleStyle(SimpleToggleStyle(shape: RoundedRectangle(cornerRadius: 5), padding: 8))
Spacer()
FancyLogo()
.frame(width: 200, height: 200)
Spacer()
VStack(spacing: 16) {
Text("Join a Zircle")
.font(.system(size: 20, weight: .bold, design: .default))
.shadow(color:Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.2), radius: 1, x: 0, y: 2)
.foregroundColor(Color.buttonBlue)
.modifier(ZcashButtonBackground(buttonShape: .roundedCorners(fillStyle: .solid(color: Color.background))))
.shadow(color: Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.3), radius: 15, x: 10, y: 15)
.shadow(color: Color.white.opacity(0.5), radius: 25, x:-10, y: -10)
.frame(height: 50)
Button(action: {}) {
Text("Create New")
.font(.system(size: 20, weight: .bold, design: .default))
.shadow(color:Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.2), radius: 1, x: 0, y: 2)
.foregroundColor(Color.buttonBlue)
.modifier(ZcashButtonBackground(buttonShape: .roundedCorners(fillStyle: .solid(color: Color.background))))
.foregroundColor(Color.background)
.modifier(ZcashButtonBackground(buttonShape: .roundedCorners(fillStyle: .solid(color: Color.buttonBlue))))
.shadow(color: Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.3), radius: 15, x: 10, y: 15)
.shadow(color: Color.white.opacity(0.5), radius: 25, x:-10, y: -10)
.shadow(color: Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.5), radius: 25, x: 10, y: 10)
.frame(height: 50)
Button(action: {}) {
Text("Create New")
.font(.system(size: 20, weight: .bold, design: .default))
.shadow(color:Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.2), radius: 1, x: 0, y: 2)
.foregroundColor(Color.background)
.modifier(ZcashButtonBackground(buttonShape: .roundedCorners(fillStyle: .solid(color: Color.buttonBlue))))
.shadow(color: Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.5), radius: 25, x: 10, y: 10)
.frame(height: 50)
}
}.padding(.all, 0)
}.padding(30)
}.navigationBarTitle(Text("Welcome to Zircles"))
}
}
}.padding(.all, 0)
}.padding(30)
}.navigationBarTitle(Text("Welcome to Zircles"))
}
}
@ -86,7 +83,7 @@ struct FancyLogo: View {
.foregroundColor(.buttonGray)
.font(
.custom("Zboto", size: 200)
).padding()
).padding()
.frame(alignment: .center)
.contentShape(Circle())
.offset(x: 0, y: 50)

View File

@ -7,11 +7,14 @@
//
import SwiftUI
import MnemonicSwift
struct WelcomeView: View {
@State var username: String = ""
@State var seedPhrase: String = ""
@State var isWelcome = false
var body: some View {
ZStack {
Color.background
Color.background.edgesIgnoringSafeArea(.all)
VStack(alignment: .leading, spacing: 16) {
Spacer()
Text("It looks like you are a new user, let's get to know you! What is your name?")
@ -26,7 +29,7 @@ struct WelcomeView: View {
.frame(alignment: .leading)
Card(isOn: .constant(true),cornerRadius: 5,padding: 8) {
TextField("some title text", text: .constant("Hackathon Happy Hour"))
TextField("some title text", text: $username)
.foregroundColor(Color.textDarkGray)
.font(.system(size: 14, weight: .heavy, design: .default))
@ -44,7 +47,7 @@ struct WelcomeView: View {
.font(.footnote)
.frame(alignment: .leading)
Card(isOn: .constant(true),cornerRadius: 5,padding: 8) {
TextField("Existing Wallet Seed Phrase", text: .constant("words words words words words"))
TextField("Existing Wallet Seed Phrase", text: $seedPhrase)
.foregroundColor(Color.textDarkGray)
.font(.system(size: 14, weight: .heavy, design: .default))
}
@ -56,14 +59,25 @@ struct WelcomeView: View {
.font(.footnote)
.frame(alignment: .center)
Spacer()
Text("Add Wallet to Zircles")
.font(.system(size: 20, weight: .bold, design: .default))
.shadow(color:Color(.sRGBLinear, red: 0.2, green: 0.2, blue: 0.2, opacity: 0.5), radius: 1, x: 0, y: 2)
.foregroundColor(Color.background)
.modifier(ZcashButtonBackground(buttonShape: .roundedCorners(fillStyle: .gradient(gradient: LinearGradient.zButtonGradient))))
Button(action: {
.shadow(color: Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.5), radius: 25, x: 10, y: 10)
.frame(height: 50)
self.isWelcome = true
}) {
Text("Add Wallet to Zircles")
.font(.system(size: 20, weight: .bold, design: .default))
.shadow(color:Color(.sRGBLinear, red: 0.2, green: 0.2, blue: 0.2, opacity: 0.5), radius: 1, x: 0, y: 2)
.foregroundColor(Color.background)
.modifier(ZcashButtonBackground(buttonShape: .roundedCorners(fillStyle: .gradient(gradient: LinearGradient.zButtonGradient))))
.shadow(color: Color(red: 0.2, green: 0.2, blue: 0.2).opacity(0.5), radius: 25, x: 10, y: 10)
.frame(height: 50)
}.disabled(!validInput())
NavigationLink(
destination: SplashScreen(),
isActive: $isWelcome,
label: {
EmptyView()
})
}
.padding([.horizontal,.bottom], 30)
@ -71,6 +85,19 @@ struct WelcomeView: View {
}.navigationBarTitle(Text("Welcome"))
}
func validInput() -> Bool {
validSeedPhrase() && validName()
}
func validSeedPhrase() -> Bool {
Mnemonic.validate(mnemonic: seedPhrase)
}
func validName() -> Bool {
!username.isEmpty
}
}
struct WelcomeView_Previews: PreviewProvider {