zashi-ios-wallet-private/secant/Features/Scan/ScanStore.swift

144 lines
4.6 KiB
Swift

//
// ScanUIView.swift
// secant-testnet
//
// Created by Lukáš Korba on 16.05.2022.
//
import ComposableArchitecture
import Foundation
typealias ScanStore = Store<ScanReducer.State, ScanReducer.Action>
typealias ScanViewStore = ViewStore<ScanReducer.State, ScanReducer.Action>
struct ScanReducer: ReducerProtocol {
private enum CancelId {}
struct State: Equatable {
enum ScanStatus: Equatable {
case failed
case value(RedactableString)
case unknown
}
@BindingState var alert: AlertState<ScanReducer.Action>?
var isTorchAvailable = false
var isTorchOn = false
var isValidValue = false
var scanStatus: ScanStatus = .unknown
var scannedValue: String? {
guard case let .value(scannedValue) = scanStatus else {
return nil
}
return scannedValue.data
}
}
@Dependency(\.captureDevice) var captureDevice
@Dependency(\.mainQueue) var mainQueue
@Dependency(\.uriParser) var uriParser
enum Action: Equatable {
case dismissAlert
case onAppear
case onDisappear
case found(RedactableString)
case scanFailed
case scan(RedactableString)
case torchPressed
}
// swiftlint:disable:next cyclomatic_complexity
func reduce(into state: inout State, action: Action) -> ComposableArchitecture.EffectTask<Action> {
switch action {
case .dismissAlert:
state.alert = nil
return .none
case .onAppear:
// reset the values
state.scanStatus = .unknown
state.isValidValue = false
state.isTorchOn = false
// check the torch availability
do {
state.isTorchAvailable = try captureDevice.isTorchAvailable()
} catch {
// TODO: [#322] Handle error more properly (https://github.com/zcash/secant-ios-wallet/issues/322)
state.alert = AlertState(
title: TextState(L10n.Scan.Alert.CantInitializeCamera.title),
message: TextState(L10n.Scan.Alert.CantInitializeCamera.message(error.localizedDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
return .none
case .onDisappear:
return .cancel(id: CancelId.self)
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
}
state.scanStatus = .value(code)
state.isValidValue = false
do {
if try uriParser.isValidURI(code.data) {
state.isValidValue = true
// 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(
EffectTask.cancel(id: CancelId.self),
EffectTask(value: .found(code))
.delay(for: 1.0, scheduler: mainQueue)
.eraseToEffect()
.cancellable(id: CancelId.self, cancelInFlight: true)
)
}
} catch {
state.scanStatus = .failed
}
return .cancel(id: CancelId.self)
case .torchPressed:
do {
try captureDevice.torch(!state.isTorchOn)
state.isTorchOn.toggle()
} catch {
// TODO: [#322] handle torch errors (https://github.com/zcash/secant-ios-wallet/issues/322)
state.alert = AlertState(
title: TextState(L10n.Scan.Alert.CantInitializeCamera.title),
message: TextState(L10n.Scan.Alert.CantInitializeCamera.message(error.localizedDescription)),
dismissButton: .default(TextState(L10n.General.ok), action: .send(.dismissAlert))
)
}
return .none
}
}
}
// MARK: Placeholders
extension ScanReducer.State {
static var placeholder: Self {
.init()
}
}
extension ScanStore {
static let placeholder = ScanStore(
initialState: .placeholder,
reducer: ScanReducer()
)
}