mirror of
https://github.com/selfxyz/self.git
synced 2026-02-09 22:06:22 -05:00
231 lines
8.3 KiB
Swift
231 lines
8.3 KiB
Swift
//
|
|
// MainView.swift
|
|
// NFCPassportReaderApp
|
|
//
|
|
// Created by Andy Qua on 04/06/2019.
|
|
// Copyright © 2019 Andy Qua. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Combine
|
|
import NFCPassportReader
|
|
import UniformTypeIdentifiers
|
|
import MRZParser
|
|
|
|
struct MainView : View {
|
|
@EnvironmentObject var settings: SettingsStore
|
|
@Environment(\.colorScheme) var colorScheme
|
|
|
|
@State private var showingAlert = false
|
|
@State private var showingSheet = false
|
|
@State private var showDetails = false
|
|
@State private var alertTitle : String = ""
|
|
@State private var alertMessage : String = ""
|
|
@State private var showSettings : Bool = false
|
|
@State private var showScanMRZ : Bool = false
|
|
@State private var showSavedPassports : Bool = false
|
|
|
|
@State var page = 0
|
|
|
|
@State var bgColor = Color( UIColor.systemBackground )
|
|
|
|
private let passportReader = PassportReader()
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
ZStack {
|
|
NavigationLink( destination: SettingsView(), isActive: $showSettings) { Text("") }
|
|
NavigationLink( destination: PassportView(), isActive: $showDetails) { Text("") }
|
|
NavigationLink( destination: StoredPassportView(), isActive: $showSavedPassports) { Text("") }
|
|
NavigationLink( destination: MRZScanner(completionHandler: { mrz in
|
|
|
|
if let (docNr, dob, doe) = parse( mrz:mrz ) {
|
|
settings.passportNumber = docNr
|
|
settings.dateOfBirth = dob
|
|
settings.dateOfExpiry = doe
|
|
}
|
|
showScanMRZ = false
|
|
}).navigationTitle("Scan MRZ"), isActive: $showScanMRZ){ Text("") }
|
|
|
|
VStack {
|
|
HStack {
|
|
Spacer()
|
|
Button(action: {self.showScanMRZ.toggle()}) {
|
|
Label("Scan MRZ", systemImage:"camera")
|
|
}.padding([.top, .trailing])
|
|
}
|
|
MRZEntryView()
|
|
|
|
Button(action: {
|
|
self.scanPassport()
|
|
}) {
|
|
Text("Scan Passport")
|
|
.font(.largeTitle)
|
|
.foregroundColor(isValid ? .secondary : Color.secondary.opacity(0.25))
|
|
}
|
|
.disabled( !isValid )
|
|
|
|
Spacer()
|
|
HStack(alignment:.firstTextBaseline) {
|
|
Text( "Version - \(UIApplication.version)" )
|
|
.font(.footnote)
|
|
.padding(.leading)
|
|
Spacer()
|
|
Button(action: {
|
|
shareLogs()
|
|
}) {
|
|
Text("Share logs")
|
|
.foregroundColor(.secondary)
|
|
}.padding(.trailing)
|
|
.disabled( !isValid )
|
|
}
|
|
}
|
|
}
|
|
.navigationBarTitle("Passport details", displayMode: .automatic)
|
|
.toolbar {
|
|
ToolbarItem(placement: .primaryAction) {
|
|
Menu {
|
|
Button(action: {showSettings.toggle()}) {
|
|
Label("Settings", systemImage: "gear")
|
|
}
|
|
Button(action: {self.showSavedPassports.toggle()}) {
|
|
Label("Show saved passports", systemImage: "doc")
|
|
}
|
|
} label: {
|
|
Image(systemName: "gear")
|
|
.foregroundColor(Color.secondary)
|
|
}
|
|
}
|
|
}
|
|
.alert(isPresented: $showingAlert) {
|
|
Alert(title: Text(alertTitle), message:
|
|
Text(alertMessage), dismissButton: .default(Text("Got it!")))
|
|
}
|
|
.background(colorScheme == .dark ? Color.black : Color.white)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: View functions - functions that affect the view
|
|
extension MainView {
|
|
|
|
var isValid : Bool {
|
|
return settings.passportNumber.count >= 8
|
|
}
|
|
|
|
func parse( mrz:String ) -> (String, Date, Date)? {
|
|
print( "mrz = \(mrz)")
|
|
|
|
let parser = MRZParser(isOCRCorrectionEnabled: true)
|
|
if let result = parser.parse(mrzString: mrz),
|
|
let docNr = result.documentNumber,
|
|
let dob = result.birthdate,
|
|
let doe = result.expiryDate {
|
|
|
|
return (docNr, dob, doe)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MARK: Action Functions
|
|
extension MainView {
|
|
|
|
func shareLogs() {
|
|
hideKeyboard()
|
|
PassportUtils.shareLogs()
|
|
}
|
|
|
|
func scanPassport( ) {
|
|
hideKeyboard()
|
|
self.showDetails = false
|
|
|
|
let df = DateFormatter()
|
|
df.timeZone = TimeZone(secondsFromGMT: 0)
|
|
df.dateFormat = "YYMMdd"
|
|
|
|
let pptNr = settings.passportNumber
|
|
let dob = df.string(from:settings.dateOfBirth)
|
|
let doe = df.string(from:settings.dateOfExpiry)
|
|
|
|
let passportUtils = PassportUtils()
|
|
let mrzKey = passportUtils.getMRZKey( passportNumber: pptNr, dateOfBirth: dob, dateOfExpiry: doe)
|
|
|
|
// Set the masterListURL on the Passport Reader to allow auto passport verification
|
|
let masterListURL = Bundle.main.url(forResource: "masterList", withExtension: ".pem")!
|
|
passportReader.setMasterListURL( masterListURL )
|
|
|
|
// Set whether to use the new Passive Authentication verification method (default true) or the old OpenSSL CMS verifiction
|
|
passportReader.passiveAuthenticationUsesOpenSSL = !settings.useNewVerificationMethod
|
|
|
|
// If we want to read only specific data groups we can using:
|
|
// let dataGroups : [DataGroupId] = [.COM, .SOD, .DG1, .DG2, .DG7, .DG11, .DG12, .DG14, .DG15]
|
|
// passportReader.readPassport(mrzKey: mrzKey, tags:dataGroups, completed: { (passport, error) in
|
|
|
|
Log.logLevel = settings.logLevel
|
|
Log.storeLogs = settings.shouldCaptureLogs
|
|
Log.clearStoredLogs()
|
|
|
|
Log.error( "Using version \(UIApplication.version)" )
|
|
|
|
Task {
|
|
let customMessageHandler : (NFCViewDisplayMessage)->String? = { (displayMessage) in
|
|
switch displayMessage {
|
|
case .requestPresentPassport:
|
|
return "Hold your iPhone near an NFC enabled passport."
|
|
default:
|
|
// Return nil for all other messages so we use the provided default
|
|
return nil
|
|
}
|
|
}
|
|
|
|
do {
|
|
let passport = try await passportReader.readPassport( mrzKey: mrzKey, customDisplayMessage:customMessageHandler)
|
|
|
|
if settings.savePassportOnScan {
|
|
// Save passport
|
|
let dict = passport.dumpPassportData(selectedDataGroups: DataGroupId.allCases, includeActiveAuthenticationData: true)
|
|
if let data = try? JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted) {
|
|
|
|
let savedPath = FileManager.cachesFolder.appendingPathComponent("\(passport.documentNumber).json")
|
|
|
|
try? data.write(to: savedPath, options: .completeFileProtection)
|
|
}
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
self.settings.passport = passport
|
|
self.showDetails = true
|
|
}
|
|
} catch {
|
|
self.alertTitle = "Oops"
|
|
self.alertTitle = error.localizedDescription
|
|
self.showingAlert = true
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//MARK: PreviewProvider
|
|
#if DEBUG
|
|
struct ContentView_Previews : PreviewProvider {
|
|
|
|
static var previews: some View {
|
|
let settings = SettingsStore()
|
|
|
|
return Group {
|
|
MainView()
|
|
.environmentObject(settings)
|
|
.environment( \.colorScheme, .light)
|
|
MainView()
|
|
.environmentObject(settings)
|
|
.environment( \.colorScheme, .dark)
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|