177 lines
5.3 KiB
Swift
177 lines
5.3 KiB
Swift
//
|
|
// ScanUIView.swift
|
|
// secant-testnet
|
|
//
|
|
// Created by Lukáš Korba on 16.05.2022.
|
|
//
|
|
|
|
import ComposableArchitecture
|
|
import Foundation
|
|
import CaptureDevice
|
|
import Utils
|
|
import URIParser
|
|
import ZcashLightClientKit
|
|
import Generated
|
|
import ZcashSDKEnvironment
|
|
|
|
public typealias ScanStore = Store<ScanReducer.State, ScanReducer.Action>
|
|
public typealias ScanViewStore = ViewStore<ScanReducer.State, ScanReducer.Action>
|
|
|
|
public struct ScanReducer: Reducer {
|
|
private enum CancelId { case timer }
|
|
|
|
public struct State: Equatable {
|
|
public enum ScanStatus: Equatable {
|
|
case failed
|
|
case value(RedactableString)
|
|
case unknown
|
|
}
|
|
|
|
@PresentationState public var alert: AlertState<Action>?
|
|
public var isTorchAvailable = false
|
|
public var isTorchOn = false
|
|
public var scanStatus: ScanStatus = .unknown
|
|
|
|
public var scannedValue: String? {
|
|
guard case let .value(scannedValue) = scanStatus else {
|
|
return nil
|
|
}
|
|
|
|
return scannedValue.data
|
|
}
|
|
|
|
public var isValidValue: Bool {
|
|
if case .value = scanStatus {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
public init(
|
|
isTorchAvailable: Bool = false,
|
|
isTorchOn: Bool = false,
|
|
scanStatus: ScanStatus = .unknown
|
|
) {
|
|
self.isTorchAvailable = isTorchAvailable
|
|
self.isTorchOn = isTorchOn
|
|
self.scanStatus = scanStatus
|
|
}
|
|
}
|
|
|
|
@Dependency(\.captureDevice) var captureDevice
|
|
@Dependency(\.mainQueue) var mainQueue
|
|
@Dependency(\.uriParser) var uriParser
|
|
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
|
|
|
|
public enum Action: Equatable {
|
|
case alert(PresentationAction<Action>)
|
|
case onAppear
|
|
case onDisappear
|
|
case found(RedactableString)
|
|
case scanFailed
|
|
case scan(RedactableString)
|
|
case torchPressed
|
|
}
|
|
|
|
public init() { }
|
|
|
|
// swiftlint:disable:next cyclomatic_complexity
|
|
public var body: some ReducerOf<Self> {
|
|
Reduce { state, action in
|
|
switch action {
|
|
case .alert(.presented(let action)):
|
|
return Effect.send(action)
|
|
|
|
case .alert(.dismiss):
|
|
state.alert = nil
|
|
return .none
|
|
|
|
case .alert:
|
|
return .none
|
|
|
|
case .onAppear:
|
|
// reset the values
|
|
state.scanStatus = .unknown
|
|
state.isTorchOn = false
|
|
// check the torch availability
|
|
do {
|
|
state.isTorchAvailable = try captureDevice.isTorchAvailable()
|
|
} catch {
|
|
state.alert = AlertState.cantInitializeCamera(error.toZcashError())
|
|
}
|
|
return .none
|
|
|
|
case .onDisappear:
|
|
return .cancel(id: CancelId.timer)
|
|
|
|
case .found:
|
|
return .none
|
|
|
|
case .scanFailed:
|
|
state.scanStatus = .failed
|
|
return .none
|
|
|
|
case .scan(let code):
|
|
// the logic for the same scanned code is skipped until some new code
|
|
if let prevCode = state.scannedValue, prevCode == code.data {
|
|
return .none
|
|
}
|
|
if uriParser.isValidURI(code.data, zcashSDKEnvironment.network.networkType) {
|
|
state.scanStatus = .value(code)
|
|
// once valid URI is scanned we want to start the timer to deliver the code
|
|
// any new code cancels the schedule and fires new one
|
|
return .concatenate(
|
|
Effect.cancel(id: CancelId.timer),
|
|
.run { send in
|
|
try await mainQueue.sleep(for: .seconds(1))
|
|
await send(.found(code))
|
|
}
|
|
.cancellable(id: CancelId.timer, cancelInFlight: true)
|
|
)
|
|
} else {
|
|
state.scanStatus = .failed
|
|
}
|
|
return .cancel(id: CancelId.timer)
|
|
|
|
case .torchPressed:
|
|
do {
|
|
try captureDevice.torch(!state.isTorchOn)
|
|
state.isTorchOn.toggle()
|
|
} catch {
|
|
state.alert = AlertState.cantInitializeCamera(error.toZcashError())
|
|
}
|
|
return .none
|
|
}
|
|
}
|
|
.ifLet(\.$alert, action: /Action.alert)
|
|
}
|
|
}
|
|
|
|
// MARK: Alerts
|
|
|
|
extension AlertState where Action == ScanReducer.Action {
|
|
public static func cantInitializeCamera(_ error: ZcashError) -> AlertState {
|
|
AlertState {
|
|
TextState(L10n.Scan.Alert.CantInitializeCamera.title)
|
|
} message: {
|
|
TextState(L10n.Scan.Alert.CantInitializeCamera.message(error.message, error.code.rawValue))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Placeholders
|
|
|
|
extension ScanReducer.State {
|
|
public static var initial: Self {
|
|
.init()
|
|
}
|
|
}
|
|
|
|
extension ScanStore {
|
|
public static let placeholder = ScanStore(
|
|
initialState: .initial
|
|
) {
|
|
ScanReducer()
|
|
}
|
|
}
|