134 lines
3.8 KiB
Swift
134 lines
3.8 KiB
Swift
|
//
|
||
|
// RecoveryPhraseDisplayStore.swift
|
||
|
// secant-testnet
|
||
|
//
|
||
|
// Created by Francisco Gindre on 10/26/21.
|
||
|
//
|
||
|
|
||
|
import Foundation
|
||
|
import ComposableArchitecture
|
||
|
import UIKit
|
||
|
|
||
|
enum RecoveryPhraseError: Error {
|
||
|
/// This error is thrown then the Recovery Phrase can't be generated
|
||
|
case unableToGeneratePhrase
|
||
|
}
|
||
|
|
||
|
struct Pasteboard {
|
||
|
let setString: (String) -> Void
|
||
|
let getString: () -> String?
|
||
|
}
|
||
|
|
||
|
extension Pasteboard {
|
||
|
private struct TestPasteboard {
|
||
|
static var general = TestPasteboard()
|
||
|
var string: String?
|
||
|
}
|
||
|
|
||
|
static let live = Pasteboard(
|
||
|
setString: { UIPasteboard.general.string = $0 },
|
||
|
getString: { UIPasteboard.general.string }
|
||
|
)
|
||
|
|
||
|
static let test = Pasteboard(
|
||
|
setString: { TestPasteboard.general.string = $0 },
|
||
|
getString: { TestPasteboard.general.string }
|
||
|
)
|
||
|
}
|
||
|
|
||
|
struct BackupPhraseEnvironment {
|
||
|
let mainQueue: AnySchedulerOf<DispatchQueue>
|
||
|
let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError>
|
||
|
let pasteboard: Pasteboard
|
||
|
}
|
||
|
|
||
|
extension BackupPhraseEnvironment {
|
||
|
private struct DemoPasteboard {
|
||
|
static var general = Self()
|
||
|
var string: String?
|
||
|
}
|
||
|
|
||
|
static let demo = Self(
|
||
|
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
|
||
|
newPhrase: { Effect(value: .init(words: RecoveryPhrase.demo.words)) },
|
||
|
pasteboard: .test
|
||
|
)
|
||
|
|
||
|
static let live = Self(
|
||
|
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
|
||
|
newPhrase: { Effect(value: .init(words: RecoveryPhrase.demo.words)) },
|
||
|
pasteboard: .live
|
||
|
)
|
||
|
}
|
||
|
|
||
|
typealias RecoveryPhraseDisplayStore = Store<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction>
|
||
|
|
||
|
struct RecoveryPhrase: Equatable {
|
||
|
struct Chunk: Hashable {
|
||
|
var startIndex: Int
|
||
|
var words: [String]
|
||
|
}
|
||
|
|
||
|
let words: [String]
|
||
|
|
||
|
private let chunkSize = 6
|
||
|
|
||
|
func toChunks() -> [Chunk] {
|
||
|
let chunks = words.count / chunkSize
|
||
|
return zip(0 ..< chunks, words.chunked(into: chunkSize)).map {
|
||
|
Chunk(startIndex: $0 * chunkSize + 1, words: $1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func toString() -> String {
|
||
|
words.joined(separator: " ")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct RecoveryPhraseDisplayState: Equatable {
|
||
|
var phrase: RecoveryPhrase?
|
||
|
var showCopyToBufferAlert = false
|
||
|
}
|
||
|
|
||
|
enum RecoveryPhraseDisplayAction: Equatable {
|
||
|
case createPhrase
|
||
|
case copyToBufferPressed
|
||
|
case finishedPressed
|
||
|
case phraseResponse(Result<RecoveryPhrase, RecoveryPhraseError>)
|
||
|
}
|
||
|
|
||
|
typealias RecoveryPhraseDisplayReducer = Reducer<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction, BackupPhraseEnvironment>
|
||
|
|
||
|
extension RecoveryPhraseDisplayReducer {
|
||
|
static let `default` = RecoveryPhraseDisplayReducer { state, action, environment in
|
||
|
switch action {
|
||
|
case .createPhrase:
|
||
|
return environment.newPhrase()
|
||
|
.receive(on: environment.mainQueue)
|
||
|
.catchToEffect(RecoveryPhraseDisplayAction.phraseResponse)
|
||
|
case .copyToBufferPressed:
|
||
|
guard let phrase = state.phrase?.toString() else { return .none }
|
||
|
environment.pasteboard.setString(phrase)
|
||
|
state.showCopyToBufferAlert = true
|
||
|
return .none
|
||
|
case .finishedPressed:
|
||
|
// TODO: remove this when feature is implemented in https://github.com/zcash/secant-ios-wallet/issues/47
|
||
|
return .none
|
||
|
case let .phraseResponse(.success(phrase)):
|
||
|
state.phrase = phrase
|
||
|
return .none
|
||
|
case .phraseResponse(.failure):
|
||
|
// TODO: remove this when feature is implemented in https://github.com/zcash/secant-ios-wallet/issues/129
|
||
|
return .none
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension Array {
|
||
|
func chunked(into size: Int) -> [[Element]] {
|
||
|
return stride(from: 0, to: count, by: size).map {
|
||
|
Array(self[$0 ..< Swift.min($0 + size, count)])
|
||
|
}
|
||
|
}
|
||
|
}
|