mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
* feat: Read ID cards * add entitlements * feat: add pacePolling through NFCPassportReader * refactor: comment out debug print statements * feat: add mixpanel logging * feat: add NFC Method selection screen * fix: UI issue * feat: update Podfile and include PassportReader.m file * lint * fix: test * debug: log mixpanel values * fix: update dep * update NFCPassportReader commit hash * feat: add flag to enable debug logging * lint
133 lines
6.0 KiB
Swift
133 lines
6.0 KiB
Swift
// LiveMRZScannerView.swift
|
|
|
|
import SwiftUI
|
|
import QKMRZParser
|
|
|
|
struct LiveMRZScannerView: View {
|
|
@State private var recognizedText: String = ""
|
|
@State private var lastMRZDetection: Date = Date()
|
|
@State private var parsedMRZ: QKMRZResult? = nil
|
|
@State private var scanComplete: Bool = false
|
|
var onScanComplete: ((QKMRZResult) -> Void)? = nil
|
|
var onScanResultAsDict: (([String: Any]) -> Void)? = nil
|
|
|
|
func singleCorrectDocumentNumberInMRZ(result: String, docNumber: String, parser: QKMRZParser) -> QKMRZResult? {
|
|
let replacements: [Character: [Character]] = [
|
|
// "0": ["O", "D"],
|
|
// "1": ["I"],
|
|
"O": ["0"],
|
|
"D": ["0"],
|
|
"I": ["1"],
|
|
"L": ["1"],
|
|
"S": ["5"],
|
|
"G": ["6"],
|
|
// "2": ["Z"], "Z": ["2"],
|
|
// "8": ["B"], "B": ["8"]
|
|
]
|
|
let lines = result.components(separatedBy: "\n")
|
|
guard lines.count >= 2 else { return nil }
|
|
for (i, char) in docNumber.enumerated() {
|
|
if let subs = replacements[char] {
|
|
for sub in subs {
|
|
var chars = Array(docNumber)
|
|
chars[i] = sub
|
|
let candidate = String(chars)
|
|
if let range = lines[1].range(of: docNumber) {
|
|
var newLine = lines[1]
|
|
let start = newLine.distance(from: newLine.startIndex, to: range.lowerBound)
|
|
var lineChars = Array(newLine)
|
|
let docNumChars = Array(candidate)
|
|
for j in 0..<min(docNumber.count, docNumChars.count) {
|
|
lineChars[start + j] = docNumChars[j]
|
|
}
|
|
newLine = String(lineChars)
|
|
var newLines = lines
|
|
newLines[1] = newLine
|
|
let correctedMRZ = newLines.joined(separator: "\n")
|
|
// print("Trying candidate: \(candidate), correctedMRZ: \(correctedMRZ)")
|
|
if let correctedResult = parser.parse(mrzString: correctedMRZ) {
|
|
if correctedResult.isDocumentNumberValid {
|
|
return correctedResult
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private func mapVisionResultToDictionary(_ result: QKMRZResult) -> [String: Any] {
|
|
return [
|
|
"documentType": result.documentType,
|
|
"countryCode": result.countryCode,
|
|
"surnames": result.surnames,
|
|
"givenNames": result.givenNames,
|
|
"documentNumber": result.documentNumber,
|
|
"nationalityCountryCode": result.nationalityCountryCode,
|
|
"dateOfBirth": result.birthdate?.description ?? "",
|
|
"sex": result.sex ?? "",
|
|
"expiryDate": result.expiryDate?.description ?? "",
|
|
"personalNumber": result.personalNumber,
|
|
"personalNumber2": result.personalNumber2 ?? "",
|
|
"isDocumentNumberValid": result.isDocumentNumberValid,
|
|
"isBirthdateValid": result.isBirthdateValid,
|
|
"isExpiryDateValid": result.isExpiryDateValid,
|
|
"isPersonalNumberValid": result.isPersonalNumberValid ?? false,
|
|
"allCheckDigitsValid": result.allCheckDigitsValid
|
|
]
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack(alignment: .bottom) {
|
|
CameraView(
|
|
frameHandler: { image, roi in
|
|
if scanComplete { return }
|
|
MRZScanner.scan(image: image, roi: roi) { result, boxes in
|
|
recognizedText = result
|
|
lastMRZDetection = Date()
|
|
// print("[LiveMRZScannerView] result: \(result)")
|
|
let parser = QKMRZParser(ocrCorrection: false)
|
|
if let mrzResult = parser.parse(mrzString: result) {
|
|
let doc = mrzResult;
|
|
if doc.allCheckDigitsValid == true && !scanComplete {
|
|
parsedMRZ = mrzResult
|
|
scanComplete = true
|
|
onScanComplete?(mrzResult)
|
|
onScanResultAsDict?(mapVisionResultToDictionary(mrzResult))
|
|
} else if doc.isDocumentNumberValid == false && !scanComplete {
|
|
if let correctedResult = singleCorrectDocumentNumberInMRZ(result: result, docNumber: doc.documentNumber, parser: parser) {
|
|
let correctedDoc = correctedResult
|
|
if correctedDoc.allCheckDigitsValid == true {
|
|
parsedMRZ = correctedResult
|
|
scanComplete = true
|
|
onScanComplete?(correctedResult)
|
|
onScanResultAsDict?(mapVisionResultToDictionary(correctedResult))
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if !scanComplete {
|
|
parsedMRZ = nil
|
|
}
|
|
}
|
|
}
|
|
},
|
|
showOverlay: false
|
|
)
|
|
|
|
VStack {
|
|
if !scanComplete {
|
|
Text("Align the animation with the MRZ on the passport.")
|
|
.font(.footnote)
|
|
.padding()
|
|
.background(Color.black.opacity(0.7))
|
|
.foregroundColor(.white)
|
|
.cornerRadius(8)
|
|
.padding(.bottom, 40)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|