Go to file
Francisco Gindre 614d3ea5e8 Step 5: Split to tab view 2021-02-24 20:16:23 -03:00
accept-zcash-poc Step 5: Split to tab view 2021-02-24 20:16:23 -03:00
accept-zcash-poc.xcodeproj Step 5: Split to tab view 2021-02-24 20:16:23 -03:00
accept-zcash-poc.xcworkspace Integrate Cocoapods and Zcash SDK 2021-02-19 10:50:38 -03:00
.gitignore add gitignore 2021-02-19 10:12:34 -03:00
LICENSE Initial commit 2021-02-19 10:05:52 -03:00
Podfile Integrate Cocoapods and Zcash SDK 2021-02-19 10:50:38 -03:00
Podfile.lock Integrate Cocoapods and Zcash SDK 2021-02-19 10:50:38 -03:00
README.md Step 5: Split to tab view 2021-02-24 20:16:23 -03:00

README.md

we-accept-zcash-ios

A Proof-of-Concept on how to build a Small iOS App that lets you accept Zcash as payment

This project is a part of the Code With Me Session for Hello Decentralization 2021.

Tag: step-0

Create an Xcode project for the app accept-zcash-pos using SwiftUI

Tag: step-1-integrate-sdk

In this step we are going to integrate the ZcashLightClientKit SDK into the project using Cocoapods.

Follow installation instructions in ZcashLightClientKit home page: https://github.com/zcash/ZcashLightClientKit

Once you have the default project building successfully.

Let's try to see if this all works

On ContentView.swift, import ZcashLightClientKit and add a text saying hello to the corresponding Zcash network.


 import ZcashLightClientKit
 struct ContentView: View {
     var body: some View {
        
        Text("Hello, Zcash \(ZcashSDK.isMainnet ? "MainNet" : "TestNet")")
             .padding()
     }
 }

Tag: step-2-the-look

We are going to import quite a few perks from the ECC Wallet. The "UI Elements" folder contains several UI components we use on our app.

Tag: step-3-import-viewing-key-scaffold

Let's make a scaffold for our first task: Importing a viewing key

We added he "Utils" folder has some tricks we learned along the way to make swift ui more usable. Like keyboard avoidance support.

We renamed the ContentView to ImportViewingKey and added the text field and a nice Zcash Logo!

Tag: step-4-import-viewing-key-for-real-and-sync-it

We have the import viewing key screen laid out. Let's put it to work! That's a little bit trickier though!

First we need to create a ZcashEnvironment were all things ZcashSDK will live

class ZcashEnvironment {
    static let `default`: ZcashEnvironment = try! ZcashEnvironment()
    
    // you can spin up your own node and lightwalletd, check https://zcash.readthedocs.io/en/latest/rtd_pages/zcashd.html
    let endpoint = LightWalletEndpoint(address: ZcashSDK.isMainnet ? "localhost" : "localhost", port: 9067, secure: true)

    var synchronizer: CombineSynchronizer
    
    private init() throws {
        let initializer = Initializer(
            cacheDbURL: try Self.cacheDbURL(),
            dataDbURL: try Self.dataDbURL(),
            pendingDbURL: try Self.pendingDbURL(),
            endpoint: endpoint,
            spendParamsURL: try Self.spendParamsURL(),
            outputParamsURL: try Self.outputParamsURL(),
            loggerProxy: logger)
        
        // this is where the magic happens
        self.synchronizer = try CombineSynchronizer(initializer: initializer)
    }
    
    /**
     Initializes the synchornizer with the given viewing key and birthday
     */
    func initialize(viewingKey: String, birthday: BlockHeight) throws {
        try self.synchronizer.initializer.initialize(viewingKeys: [viewingKey], walletBirthday: birthday)
    }
}

Let's wire up the ZcashEnvironment

struct ImportViewingKey: View {

    @EnvironmentObject var model: ZcashPoSModel
    @Environment(\.zcashEnvironment) var zcash: ZcashEnvironment // this is where your zcash stuff lives

Then we need to turn that dummy button into something meaningful.

// let's make a navigation link that goes to a new screen called HomeScreen. 
NavigationLink(destination: AppNavigation.Screen.home.buildScreen(), tag: AppNavigation.Screen.home , selection: $model.navigation
    ) {
        Button(action: {
            do {
                let bday = validStringToBirthday(birthday)
                try zcash.initialize(viewingKey: ivk, birthday: bday)
                // now that we initialized the zcash environment let's save the viewing key and birthday
                model.birthday = bday
                model.viewingKey = ivk
                
                // let's navigate to the next screen
                model.navigation = AppNavigation.Screen.home
            } catch {
                
                // if something does wrong, let's do nothing and show an Alert!
                self.alertType = .errorAlert(error)
            }
        }) {
            Text("Import Viewing Key")
                .foregroundColor(.black)
                .zcashButtonBackground(shape: .roundedCorners(fillStyle: .gradient(gradient: .zButtonGradient)))
        }

In our HomeScreen view we are going to show little to nothing for now.

the important thing is that we are going to inject our PoS model and the Zcash environment.

For now we are going to say that out app is either going to be ready, syncing or offline

struct HomeScreen: View {
    enum Status {
        case ready
        case syncing
        case offline
    }
    
    @EnvironmentObject var model: ZcashPoSModel
    @Environment(\.zcashEnvironment) var zcash: ZcashEnvironment
VStack(alignment: .center, spacing: 20) {
    ZcashLogo()
    switch status {
    case .offline:
         Text("Offline").foregroundColor(.white)
    case .ready:
         Text("Ready! Yay!").foregroundColor(.white)
    case .syncing:
         Text("Syncing \(progress)% Block: \(height)").foregroundColor(.white)
    }
    
    Button(action: {
        zcash.synchronizer.stop()
        model.nuke()
    }) {
        Text("Stop And Nuke")
            .foregroundColor(.red)
            .font(.title3)
    }
}

On our main app we will have to make room for the Home screen so we will have to change the way we initialize it.

struct accept_zcash_pocApp: App {
    @StateObject private var model = ZcashPoSModel()
    
    var body: some Scene {
        WindowGroup {
            // we can navigate now!
            NavigationView {
                // and we need to check what our main screen will be. Is it an empty or an already initialized app?
                model.initialScreen()
                    .environmentObject(model)
                    .zcashEnvironment(ZcashEnvironment.default)
                    
            }
        }
    }
}

We will consider our app to be empty when it has no viewing keys loaded

var appStatus: AppNavigation.AppStatus {
    guard let vk = self.viewingKey, ((try? DerivationTool.default.isValidExtendedViewingKey(vk)) != nil) else {
        return .empty
    }
    return .initialized
}

On the other hand it's possible that we don't want to use this viewing key on this device anymore, so we added a NUKE function to clear it out.

func nuke() {
    self.birthday = nil
    self.viewingKey = nil
}

If you diff this commit you will see that there are a lot of changes and other files. Think of it as a cooking show with some pre-arrangements made for the sake of brevity. We encourage you to look at those changes!

Tag: step-5-split-to-tab-view

We are going to change the HomeScreen into a TabView

so we will move its contents to the SellView, other tab is going to be the ReceivedTransactions and a Settings tab where we will be moving the nuking button for now

struct HomeScreen: View {
 
    @Environment(\.zcashEnvironment) var zcash: ZcashEnvironment
    @EnvironmentObject var model: ZcashPoSModel
    @State var alertType: AlertType? = nil

    var body: some View {

            TabView {
                SellScreen()
                    .tabItem {
                        Label("Sell", systemImage: "shield")
                    }
                ReceivedTransactions()
                    .tabItem {
                        Label("History", systemImage: "square.and.pencil")
                    }
                
                SettingsScreen()
                    .tabItem {
                        Label("Settings", systemImage: "list.dash")
                    }
                

            }
        .navigationBarHidden(false)
        .onAppear() {
            _zECCWalletNavigationBarLookTweaks()
            do {
                guard let ivk = model.viewingKey, let bday = model.birthday else {
                    throw ZcashPoSModel.PoSError.unableToRetrieveCredentials
                }
                try self.zcash.synchronizer.initializer.initialize(viewingKeys: [ivk], walletBirthday: bday)
                try self.zcash.synchronizer.start()
            } catch {
                self.alertType = AlertType.errorAlert(error)
            }
        }
        .alert(item: $alertType) { (type) -> Alert in
            type.buildAlert()
        }
    }
}