starting to wire app the screens
This commit is contained in:
parent
a617a4aefa
commit
3abd221b82
4
Podfile
4
Podfile
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
let logger = SimpleLogger(logLevel: .debug)
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()) ?? []
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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])
|
||||
}
|
||||
}
|
|
@ -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")!
|
||||
}
|
||||
}
|
|
@ -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) ?? ""
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue