Skip to content

Commit

Permalink
Bech32 addressese now working, with QR scanning.
Browse files Browse the repository at this point in the history
  • Loading branch information
Sajjon committed Jun 15, 2019
1 parent 1f9cb7c commit 443fa27
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 33 deletions.
10 changes: 10 additions & 0 deletions Source/Application/Generated/SwiftGen/L10n-Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,16 @@ internal enum L10n {
internal enum ScanQRCode {
/// Scan QR
internal static let title = L10n.tr("Localizable", "Scene.ScanQRCode.Title")
internal enum Event {
internal enum Toast {
internal enum IncompatibleQrCode {
/// Dismiss
internal static let dismiss = L10n.tr("Localizable", "Scene.ScanQRCode.Event.Toast.IncompatibleQrCode.Dismiss")
/// QR code contains no compatible Zilliqa address
internal static let title = L10n.tr("Localizable", "Scene.ScanQRCode.Event.Toast.IncompatibleQrCode.Title")
}
}
}
}
internal enum Settings {
/// Settings
Expand Down
2 changes: 2 additions & 0 deletions Source/Application/Localization/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@

// MARK: ScanQRCode
"Scene.ScanQRCode.Title" = "Scan QR";
"Scene.ScanQRCode.Event.Toast.IncompatibleQrCode.Title" = "QR code contains no compatible Zilliqa address";
"Scene.ScanQRCode.Event.Toast.IncompatibleQrCode.Dismiss" = "Dismiss";

// MARK: SignTransaction
"Scene.SignTransaction.Label.SignTransactionWithEncryptionPassword" = "Confirm transaction with your password";
Expand Down
68 changes: 48 additions & 20 deletions Source/Models/TransactionIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,48 @@
import Foundation
import Zesame

struct TransactionIntent: Codable {
let to: LegacyAddress
extension Address: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let addressString = try container.decode(String.self).lowercased()
try self.init(string: addressString)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.asString.uppercased())
}
}

struct TransactionIntent: Codable, Equatable {
let to: Address
let amount: ZilAmount?

init(to recipient: LegacyAddress, amount: ZilAmount? = nil) {
init(to recipient: Address, amount: ZilAmount? = nil) {
self.to = recipient
self.amount = amount
}

init(to address: Address, amount: ZilAmount? = nil) throws {
let recipient = try address.toChecksummedLegacyAddress()
self.init(to: recipient, amount: amount)
}
}

extension TransactionIntent {
static func fromScannedQrCodeString(_ scannedString: String) -> TransactionIntent? {
if let address = try? Address(string: scannedString) {
return try? TransactionIntent(to: address)
} else {
guard
let json = scannedString.data(using: .utf8),
let transaction = try? JSONDecoder().decode(TransactionIntent.self, from: json) else {
return nil
}
return transaction
static func fromScannedQrCodeString(_ scannedString: String) throws -> TransactionIntent {
do {
return TransactionIntent(to: try Address(string: scannedString))
} catch {
guard let json = scannedString.data(using: .utf8) else { throw Error.scannedStringNotAddressNorJson }
return try JSONDecoder().decode(TransactionIntent.self, from: json)
}
}

enum Error: Swift.Error, Equatable {
case scannedStringNotAddressNorJson
}
}

extension TransactionIntent {
init?(to recipientString: String, amount: String?) {
guard let recipient = try? Address(string: recipientString) else { return nil }
try? self.init(to: recipient, amount: ZilAmount.fromQa(optionalString: amount))
self.init(to: recipient, amount: ZilAmount.fromQa(optionalString: amount))
}

init?(queryParameters params: [URLQueryItem]) {
Expand All @@ -71,14 +79,34 @@ extension TransactionIntent {

var queryItems: [URLQueryItem] {
return dictionaryRepresentation.compactMap {
URLQueryItem(name: $0.key, value: String(describing: $0.value))
URLQueryItem(name: $0.key, value: String(describing: $0.value).lowercased())
}.sorted(by: { $0.name.count < $1.name.count })
}
}

// MARK: - Codable
extension TransactionIntent {
enum CodingKeys: CodingKey {
case to, amount
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
to = try container.decode(Address.self, forKey: .to)
amount = try container.decodeIfPresent(ZilAmount.self, forKey: .amount)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(to, forKey: .to)
try container.encodeIfPresent(amount, forKey: .amount)
}
}

private extension ZilAmount {
static func fromQa(optionalString: String?) -> ZilAmount? {
guard let qaAmountString = optionalString else { return nil }
return try? ZilAmount(qa: qaAmountString)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ final class PrepareTransactionViewModel: BaseViewModel<
// MARK: Recipient Input -> Value + Validation
let recipientValidationValue: Driver<Validation<Address, AddressValidator.Error>> = Driver.merge(
input.fromView.recepientAddress.map { validator.validateRecipient($0) },
scannedOrDeeplinkedTransaction.map { .valid(Address.legacy($0.to)) }
scannedOrDeeplinkedTransaction.map { .valid($0.to) }
)

let recipient: Driver<Address?> = recipientValidationValue.map { $0.value }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private typealias € = L10n.Scene.ScanQRCode

final class ScanQRCodeView: UIView {

private let scannedQrCodeSubject = PublishSubject<String>()
private let scannedQrCodeSubject = PublishSubject<String?>()
private lazy var readerView = QRCodeReaderView()
private lazy var reader = QRCodeReader(metadataObjectTypes: [.qr], captureDevicePosition: .back)

Expand Down Expand Up @@ -67,8 +67,10 @@ private extension ScanQRCodeView {
reader.didFindCode = { [unowned self] in
self.scannedQrCodeSubject.onNext($0.value)
}

reader.startScanning()

reader.didFailDecoding = { [unowned self] in
self.scannedQrCodeSubject.onNext(nil)
}

readerView.switchCameraButton?.addTarget(self, action: #selector(switchCameraAction), for: .primaryActionTriggered)
readerView.toggleTorchButton?.addTarget(self, action: #selector(toggleTorchAction), for: .primaryActionTriggered)
Expand All @@ -91,4 +93,12 @@ extension ScanQRCodeView: ViewModelled {
scannedQrCodeString: scannedQrCodeSubject.asDriverOnErrorReturnEmpty()
)
}

func populate(with viewModel: ScanQRCodeViewModel.Output) -> [Disposable] {
return [
viewModel.startScanning.do(onNext: { [weak self] in
self?.reader.startScanning()
}).drive()
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,35 +39,61 @@ final class ScanQRCodeViewModel: BaseViewModel<
ScanQRCodeViewModel.InputFromView,
ScanQRCodeViewModel.Output
> {

typealias ScannedQRResult = Result<TransactionIntent, Swift.Error>

private let startScanningSubject = BehaviorSubject(value: ())

// swiftlint:disable:next function_body_length
override func transform(input: Input) -> Output {
func userDid(_ userAction: NavigationStep) {
navigator.next(userAction)
}

let transactionIntent = input.fromView.scannedQrCodeString.map {
TransactionIntent.fromScannedQrCodeString($0)
}.filterNil()
let transactionIntentResult: Driver<ScannedQRResult> = input.fromView.scannedQrCodeString.map {
guard let stringFromQR = $0 else {
return ScannedQRResult.failure(TransactionIntent.Error.scannedStringNotAddressNorJson)
}

do {
return ScannedQRResult.success(try TransactionIntent.fromScannedQrCodeString(stringFromQR))
} catch {
return ScannedQRResult.failure(error)
}
}

// MARK: Navigate
bag <~ [
input.fromController.leftBarButtonTrigger
.do(onNext: { userDid(.cancel) })
.drive(),

transactionIntent.do(onNext: { userDid(.scanQRContainingTransaction($0)) })
.drive()
transactionIntentResult.do(onNext: { [unowned self] in
switch $0 {
case .failure:
let LocalizedToast =.Event.Toast.IncompatibleQrCode.self
let toast = Toast(LocalizedToast.title, dismissing: .manual(dismissButtonTitle: LocalizedToast.dismiss)) {
self.startScanningSubject.onNext(())
}
input.fromController.toastSubject.onNext(toast)
case .success(let transactionIntent): userDid(.scanQRContainingTransaction(transactionIntent))
}
}).drive()
]

return Output()
return Output(
startScanning: startScanningSubject.asDriverOnErrorReturnEmpty()
)
}
}

extension ScanQRCodeViewModel {

struct InputFromView {
let scannedQrCodeString: Driver<String>
let scannedQrCodeString: Driver<String?>
}

struct Output {}
struct Output {
let startScanning: Driver<Void>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ final class ReceiveViewModel: BaseViewModel<
)

let transactionToReceive = Driver.combineLatest(
wallet.map { $0.legacyAddress },
wallet.map { Address.bech32($0.bech32Address) },
amount.filterNil()
) { TransactionIntent(to: $0, amount: $1) }

Expand Down

0 comments on commit 443fa27

Please sign in to comment.