diff --git a/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.pbxproj b/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.pbxproj index 54ca705..8aa73f5 100644 --- a/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.pbxproj +++ b/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.pbxproj @@ -40,6 +40,11 @@ F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */; }; F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */; }; F3203BF423CD140100265268 /* ContactQRCodeRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */; }; + F3203BFB23CD430500265268 /* QRCodeScannerView+Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */; }; + F3203BFC23CD430500265268 /* QRCodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BFA23CD430500265268 /* QRCodeScannerView.swift */; }; + F3203BFF23CDF6D100265268 /* ScannerViewControlller.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */; }; + F326140823CE3928009EC215 /* ContactsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326140723CE3928009EC215 /* ContactsState.swift */; }; + F326140A23CE689A009EC215 /* Contact+InitHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326140923CE689A009EC215 /* Contact+InitHelpers.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -75,6 +80,11 @@ F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserQRCodeFormView.swift; sourceTree = ""; }; F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserQRCodeFormView+ViewModel.swift"; sourceTree = ""; }; F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactQRCodeRepresentable.swift; sourceTree = ""; }; + F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodeScannerView+Coordinator.swift"; sourceTree = ""; }; + F3203BFA23CD430500265268 /* QRCodeScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerView.swift; sourceTree = ""; }; + F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannerViewControlller.swift; sourceTree = ""; }; + F326140723CE3928009EC215 /* ContactsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsState.swift; sourceTree = ""; }; + F326140923CE689A009EC215 /* Contact+InitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Contact+InitHelpers.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -134,10 +144,10 @@ F3203B9823C760BF00265268 /* App */ = { isa = PBXGroup; children = ( - F3203B9D23C7610400265268 /* LaunchScreen.storyboard */, - F3203B8323C7609500265268 /* SceneDelegate.swift */, F3203B8123C7609500265268 /* AppDelegate.swift */, F3203BA923C82CB100265268 /* CurrentApplication.swift */, + F3203B8323C7609500265268 /* SceneDelegate.swift */, + F3203B9D23C7610400265268 /* LaunchScreen.storyboard */, ); path = App; sourceTree = ""; @@ -145,9 +155,9 @@ F3203B9923C760C300265268 /* Scenes */ = { isa = PBXGroup; children = ( + F3203BB023C8345400265268 /* RootView.swift */, F3203BAD23C8339400265268 /* Collected Contacts */, F3203BAC23C8337F00265268 /* User QR Code */, - F3203BB023C8345400265268 /* RootView.swift */, ); path = Scenes; sourceTree = ""; @@ -155,10 +165,10 @@ F3203B9A23C760C700265268 /* Data */ = { isa = PBXGroup; children = ( - F3203BAB23C831E300265268 /* Models */, - F3203BA823C82C8F00265268 /* State */, - F3203B8523C7609500265268 /* QRConnections.xcdatamodeld */, F3203BA623C7636600265268 /* CoreDataManager+Utils.swift */, + F3203BAB23C831E300265268 /* Models */, + F3203B8523C7609500265268 /* QRConnections.xcdatamodeld */, + F3203BA823C82C8F00265268 /* State */, ); path = Data; sourceTree = ""; @@ -166,8 +176,9 @@ F3203B9B23C760C900265268 /* Reusables */ = { isa = PBXGroup; children = ( - F3203BF223CD13F300265268 /* Protocols */, + F3203BF823CD42F400265268 /* UIKit Wrappers */, F3203BD023C9DFDF00265268 /* ImageFilterService.swift */, + F3203BF223CD13F300265268 /* Protocols */, ); path = Reusables; sourceTree = ""; @@ -185,6 +196,7 @@ children = ( F3203BD223C9F20C00265268 /* AppState.swift */, F3203BD423C9F3E900265268 /* UserProfileState.swift */, + F326140723CE3928009EC215 /* ContactsState.swift */, ); path = State; sourceTree = ""; @@ -200,11 +212,11 @@ F3203BAC23C8337F00265268 /* User QR Code */ = { isa = PBXGroup; children = ( - F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */, F3203BEA23CC977000265268 /* UserQRCodeContainerView.swift */, - F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */, F3203BEC23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift */, + F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */, F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */, + F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */, ); path = "User QR Code"; sourceTree = ""; @@ -213,8 +225,8 @@ isa = PBXGroup; children = ( F3203BB223C8368800265268 /* ContactsContainerView.swift */, - F3203BB623C844F100265268 /* ContactsListView.swift */, F3203BB423C841A700265268 /* ContactsContainerView+ViewModel.swift */, + F3203BB623C844F100265268 /* ContactsListView.swift */, ); path = "Collected Contacts"; sourceTree = ""; @@ -225,6 +237,7 @@ F3203BB823C8467000265268 /* Contact+FilterState.swift */, F3203BBB23C8579400265268 /* Contact+CoreDataClass.swift */, F3203BBC23C8579400265268 /* Contact+CoreDataProperties.swift */, + F326140923CE689A009EC215 /* Contact+InitHelpers.swift */, F3203BC123C8588E00265268 /* Contact+FetchHelpers.swift */, F3203BBF23C8584500265268 /* Contact+Status.swift */, ); @@ -249,6 +262,24 @@ path = Protocols; sourceTree = ""; }; + F3203BF823CD42F400265268 /* UIKit Wrappers */ = { + isa = PBXGroup; + children = ( + F3203BFD23CDF68D00265268 /* Scanner View */, + ); + path = "UIKit Wrappers"; + sourceTree = ""; + }; + F3203BFD23CDF68D00265268 /* Scanner View */ = { + isa = PBXGroup; + children = ( + F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */, + F3203BFA23CD430500265268 /* QRCodeScannerView.swift */, + F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */, + ); + path = "Scanner View"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -334,11 +365,13 @@ F3203BB323C8368800265268 /* ContactsContainerView.swift in Sources */, F3203BC923C871D300265268 /* PreviewDevice+Utils.swift in Sources */, F3203BB923C8467000265268 /* Contact+FilterState.swift in Sources */, + F3203BFB23CD430500265268 /* QRCodeScannerView+Coordinator.swift in Sources */, F3203BC223C8588F00265268 /* Contact+FetchHelpers.swift in Sources */, F3203BC023C8584500265268 /* Contact+Status.swift in Sources */, F3203BA723C7636600265268 /* CoreDataManager+Utils.swift in Sources */, F3203BCF23C9DAE100265268 /* UserQRCodeView.swift in Sources */, F3203B8223C7609500265268 /* AppDelegate.swift in Sources */, + F326140823CE3928009EC215 /* ContactsState.swift in Sources */, F3203BB123C8345400265268 /* RootView.swift in Sources */, F3203BC723C8717F00265268 /* SampleData+Contacts.swift in Sources */, F3203BD323C9F20C00265268 /* AppState.swift in Sources */, @@ -346,9 +379,11 @@ F3203BB723C844F100265268 /* ContactsListView.swift in Sources */, F3203BED23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift in Sources */, F3203BB523C841A700265268 /* ContactsContainerView+ViewModel.swift in Sources */, + F326140A23CE689A009EC215 /* Contact+InitHelpers.swift in Sources */, F3203BEB23CC977000265268 /* UserQRCodeContainerView.swift in Sources */, F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */, F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */, + F3203BFF23CDF6D100265268 /* ScannerViewControlller.swift in Sources */, F3203BE223CA31EB00265268 /* SampleData+AppStore.swift in Sources */, F3203BD123C9DFDF00265268 /* ImageFilterService.swift in Sources */, F3203B8723C7609500265268 /* QRConnections.xcdatamodeld in Sources */, @@ -357,6 +392,7 @@ F3203BC523C8714700265268 /* SampleData.swift in Sources */, F3203BD523C9F3E900265268 /* UserProfileState.swift in Sources */, F3203B8423C7609500265268 /* SceneDelegate.swift in Sources */, + F3203BFC23CD430500265268 /* QRCodeScannerView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fc8eb0e..fd509db 100644 --- a/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/day-079/Projects/QRConnections/QRConnections.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/CypherPoet/CypherPoetCoreDataKit.git", "state": { "branch": null, - "revision": "5400fc3983f4174cd36cc79b24b7ef54affecf6b", - "version": "0.0.5" + "revision": "e14a3abf3e679d41c9e0fcc55d3e4d7ccd8ce1b6", + "version": "0.0.6" } }, { diff --git a/day-079/Projects/QRConnections/QRConnections/App/AppDelegate.swift b/day-079/Projects/QRConnections/QRConnections/App/AppDelegate.swift index 3254e1b..9fafad7 100644 --- a/day-079/Projects/QRConnections/QRConnections/App/AppDelegate.swift +++ b/day-079/Projects/QRConnections/QRConnections/App/AppDelegate.swift @@ -9,6 +9,7 @@ import UIKit import CoreData + @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { diff --git a/day-079/Projects/QRConnections/QRConnections/Data/Models/Contact/Contact+InitHelpers.swift b/day-079/Projects/QRConnections/QRConnections/Data/Models/Contact/Contact+InitHelpers.swift new file mode 100644 index 0000000..77d4d21 --- /dev/null +++ b/day-079/Projects/QRConnections/QRConnections/Data/Models/Contact/Contact+InitHelpers.swift @@ -0,0 +1,28 @@ +// +// Contact+InitHelpers.swift +// QRConnections +// +// Created by CypherPoet on 1/14/20. +// ✌️ +// + +import Foundation +import CoreData + + +extension Contact { + + static func make( + fromName name: String, + andStatus status: Status = .uncontacted, + using context: NSManagedObjectContext + ) -> Contact { + let contact = Contact(context: context) + + contact.name = name + contact.uuid = UUID() + contact.status = status + + return contact + } +} diff --git a/day-079/Projects/QRConnections/QRConnections/Data/State/AppState.swift b/day-079/Projects/QRConnections/QRConnections/Data/State/AppState.swift index 44943c4..9f2b093 100644 --- a/day-079/Projects/QRConnections/QRConnections/Data/State/AppState.swift +++ b/day-079/Projects/QRConnections/QRConnections/Data/State/AppState.swift @@ -12,6 +12,7 @@ import CypherPoetSwiftUIKit_DataFlowUtils struct AppState { + var contactsState = ContactsState() var userProfileState = UserProfileState() } @@ -22,6 +23,7 @@ struct AppState { enum AppAction { + case contacts(_ action: ContactsAction) case userProfile(_ action: UserProfileAction) } @@ -31,6 +33,8 @@ let appReducer = Reducer { appState, action in switch action { case .userProfile(let action): userProfileReducer.reduce(&appState.userProfileState, action) + case .contacts(let action): + contactsReducer.reduce(&appState.contactsState, action) } } diff --git a/day-079/Projects/QRConnections/QRConnections/Data/State/ContactsState.swift b/day-079/Projects/QRConnections/QRConnections/Data/State/ContactsState.swift new file mode 100644 index 0000000..f4f71f3 --- /dev/null +++ b/day-079/Projects/QRConnections/QRConnections/Data/State/ContactsState.swift @@ -0,0 +1,63 @@ +// +// ContactsState.swift +// QRConnections +// +// Created by CypherPoet on 1/14/20. +// ✌️ +// + + +import Foundation +import Combine +import CypherPoetSwiftUIKit_DataFlowUtils + + + +struct ContactsState { + var saveErrorMessage: String? +} + + + +enum ContactsSideEffect: SideEffect { + case save(_ contact: Contact) + + + func mapToAction() -> AnyPublisher { + switch self { + case .save(let contact): + guard let context = contact.managedObjectContext else { + return Just(AppAction.contacts(.saveErrorMessageSet("No managed object context found"))) + .eraseToAnyPublisher() + } + + return Just(context) + .flatMap { context in + CurrentApp.coreDataManager.save(context) + .map { AppAction.contacts(.saveErrorMessageSet(nil)) } + .catch { error in + Just(AppAction.contacts(.saveErrorMessageSet(error.localizedDescription))) + } + } + .eraseToAnyPublisher() + } + } +} + + + +enum ContactsAction { + case saveErrorMessageSet(String?) +} + + +// MARK: - Reducer +let contactsReducer: Reducer = Reducer( + reduce: { state, action in + switch action { + case .saveErrorMessageSet(let message): + state.saveErrorMessage = message + } + } +) + diff --git a/day-079/Projects/QRConnections/QRConnections/Info.plist b/day-079/Projects/QRConnections/QRConnections/Info.plist index 9742bf0..15d67b0 100644 --- a/day-079/Projects/QRConnections/QRConnections/Info.plist +++ b/day-079/Projects/QRConnections/QRConnections/Info.plist @@ -49,6 +49,8 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSCameraUsageDescription + This app would like to scan QR codes in order to add to your collection of contacts. UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/QRCodeScannerView+Coordinator.swift b/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/QRCodeScannerView+Coordinator.swift new file mode 100644 index 0000000..775b038 --- /dev/null +++ b/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/QRCodeScannerView+Coordinator.swift @@ -0,0 +1,60 @@ +// +// QRCodeScannerView+Coordinator.swift +// QRConnections +// +// Created by CypherPoet on 1/13/20. +// ✌️ +// + +import Foundation +import UIKit +import AVFoundation + + +// MARK: - Coordinator +extension QRCodeScannerView { + + class Coordinator: NSObject { + let onScanCompleted: QRCodeScannerView.CompletionHandler + let simulatedData: String + + init( + simulatedData: String, + onScanCompleted: @escaping QRCodeScannerView.CompletionHandler + ) { + self.simulatedData = simulatedData + self.onScanCompleted = onScanCompleted + } + } +} + + +// MARK: - ScannerViewControllerCaptureSessionDelegate +extension QRCodeScannerView.Coordinator: ScannerViewControllerCaptureSessionDelegate { + + func captureSession(didScan metadataObject: ScannerViewController.CapturedOutput) { + #if targetEnvironment(simulator) + onScanCompleted(.success(simulatedData)) + + #else + + guard + let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject, + let stringValue = readableObject.stringValue else + { return } + + AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) + + onScanCompleted(.success(stringValue)) + + #endif + } + + func captureSessionWasUnableToAddInput() { + onScanCompleted(.failure(.badInput)) + } + + func captureSessionWasUnableToAddOutput() { + onScanCompleted(.failure(.badOutput)) + } +} diff --git a/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/QRCodeScannerView.swift b/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/QRCodeScannerView.swift new file mode 100644 index 0000000..75a8184 --- /dev/null +++ b/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/QRCodeScannerView.swift @@ -0,0 +1,70 @@ +// +// QRCodeScannerView.swift +// QRConnections +// +// Created by CypherPoet on 1/13/20. +// ✌️ +// + + +import SwiftUI +import AVFoundation + + +struct QRCodeScannerView { + typealias UIViewControllerType = ScannerViewController + typealias Completion = Result + typealias CompletionHandler = ((Completion) -> Void) + + var codeTypes: [AVMetadataObject.ObjectType] = [.qr] + var simulatedData = "" + let onScanCompleted: CompletionHandler +} + + +extension QRCodeScannerView { + enum ScanError: Error { + case badInput + case badOutput + } +} + +// MARK: - UIViewControllerRepresentable +extension QRCodeScannerView: UIViewControllerRepresentable { + + func makeCoordinator() -> Self.Coordinator { + Self.Coordinator( + simulatedData: simulatedData, + onScanCompleted: onScanCompleted + ) + } + + + func makeUIViewController( + context: UIViewControllerRepresentableContext + ) -> UIViewControllerType { + let viewController = ScannerViewController() + + viewController.codeTypes = codeTypes + viewController.captureSessionDelegate = context.coordinator + + return viewController + } + + + func updateUIViewController( + _ uiViewController: UIViewControllerType, + context: UIViewControllerRepresentableContext + ) { + } +} + + + +// MARK: - Preview +struct QRCodeScannerView_Previews: PreviewProvider { + + static var previews: some View { + QRCodeScannerView(onScanCompleted: { _ in }) + } +} diff --git a/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/ScannerViewControlller.swift b/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/ScannerViewControlller.swift new file mode 100644 index 0000000..11fa0b6 --- /dev/null +++ b/day-079/Projects/QRConnections/QRConnections/Reusables/UIKit Wrappers/Scanner View/ScannerViewControlller.swift @@ -0,0 +1,171 @@ +// +// ScannerViewControlller.swift +// QRConnections +// +// Created by CypherPoet on 1/14/20. +// ✌️ +// + +import AVFoundation +import UIKit + + +protocol ScannerViewControllerCaptureSessionDelegate: class { + func captureSession(didScan metadataObject: ScannerViewController.CapturedOutput) + func captureSessionWasUnableToAddInput() + func captureSessionWasUnableToAddOutput() +} + + +public final class ScannerViewController: UIViewController { + private var captureSession: AVCaptureSession! + private var previewLayer: AVCaptureVideoPreviewLayer! + + var codeTypes: [AVMetadataObject.ObjectType] = [.qr] + weak var captureSessionDelegate: ScannerViewControllerCaptureSessionDelegate? +} + + +extension ScannerViewController { + #if targetEnvironment(simulator) + typealias CapturedOutput = String + #else + typealias CapturedOutput = AVMetadataObject + #endif +} + + + +// MARK: - Computeds +extension ScannerViewController { + + override public var prefersStatusBarHidden: Bool { + return true + } + + override public var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } +} + + +// MARK: - View Controller Lifecycle + +#if targetEnvironment(simulator) + +extension ScannerViewController { + + override public func loadView() { + view = UIView() + + let label = UILabel() + + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + label.text = "You're running in the simulator, which means the camera isn't available. Tap anywhere to send back some simulated data." + + view.addSubview(label) + + NSLayoutConstraint.activate([ + label.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + label.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + label.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + label.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor) + ]) + } + + + override public func touchesBegan(_ touches: Set, with event: UIEvent?) { + captureSessionDelegate?.captureSession(didScan: "") + dismiss(animated: true) + } +} + + +#else + + +extension ScannerViewController { + + override public func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.black + captureSession = AVCaptureSession() + + + guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return } + guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else { return } + + + if (captureSession.canAddInput(videoInput)) { + captureSession.addInput(videoInput) + } else { + captureSessionDelegate?.captureSessionWasUnableToAddInput() + return + } + + + let metadataOutput = AVCaptureMetadataOutput() + + if (captureSession.canAddOutput(metadataOutput)) { + captureSession.addOutput(metadataOutput) + + metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + metadataOutput.metadataObjectTypes = codeTypes + } else { + captureSessionDelegate?.captureSessionWasUnableToAddOutput() + return + } + + + previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + previewLayer.frame = view.layer.bounds + previewLayer.videoGravity = .resizeAspectFill + + view.layer.addSublayer(previewLayer) + + captureSession.startRunning() + } + + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if (captureSession?.isRunning == false) { + captureSession.startRunning() + } + } + + + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if (captureSession?.isRunning == true) { + captureSession.stopRunning() + } + } +} + +#endif + + +#if targetEnvironment(simulator) +#else +extension ScannerViewController: AVCaptureMetadataOutputObjectsDelegate { + + public func metadataOutput( + _ output: AVCaptureMetadataOutput, + didOutput metadataObjects: [AVMetadataObject], + from connection: AVCaptureConnection + ) { + captureSession.stopRunning() + + if let metadataObject = metadataObjects.first { + captureSessionDelegate?.captureSession(didScan: metadataObject) + } + + dismiss(animated: true) + } +} +#endif diff --git a/day-079/Projects/QRConnections/QRConnections/Scenes/Collected Contacts/ContactsContainerView.swift b/day-079/Projects/QRConnections/QRConnections/Scenes/Collected Contacts/ContactsContainerView.swift index 2db7a74..51912bd 100644 --- a/day-079/Projects/QRConnections/QRConnections/Scenes/Collected Contacts/ContactsContainerView.swift +++ b/day-079/Projects/QRConnections/QRConnections/Scenes/Collected Contacts/ContactsContainerView.swift @@ -9,13 +9,17 @@ import SwiftUI -struct ContactsContainerView: View { +struct ContactsContainerView { + @EnvironmentObject private var store: AppStore + @ObservedObject var viewModel = ViewModel() + + @State private var isShowingScannerView = false } -// MARK: - Body -extension ContactsContainerView { +// MARK: - View +extension ContactsContainerView: View { var body: some View { NavigationView { @@ -27,6 +31,12 @@ extension ContactsContainerView { } .navigationBarTitle("Collected Contacts") .navigationBarItems(trailing: addContactButton) + .sheet(isPresented: $isShowingScannerView) { + QRCodeScannerView( + simulatedData: "🚀 Rocket Man", + onScanCompleted: self.codeScanCompleted(_:) + ) + } } } } @@ -56,7 +66,7 @@ extension ContactsContainerView { private var addContactButton: some View { Button(action: { - + self.isShowingScannerView = true }) { Image(systemName: "qrcode.viewfinder") .imageScale(.large) @@ -66,6 +76,30 @@ extension ContactsContainerView { } +// MARK: - Private Helpers +private extension ContactsContainerView { + + func codeScanCompleted(_ result: QRCodeScannerView.Completion) { + switch result { + case .success(let qrCodeString): + createNewContact(from: qrCodeString) +// self.store.send(ContactsSideEffect.createContact(qrCodeString)) + case .failure(let error): + print("Scanning failed. Error \(error.localizedDescription)") + } + } + + + func createNewContact(from qrCodeString: String) { + let context = CurrentApp.coreDataManager.backgroundContext + let contact = Contact.make(fromName: qrCodeString, using: context) + + self.store.send(ContactsSideEffect.save(contact)) + } +} + + + // MARK: - Preview struct ContactsContainerView_Previews: PreviewProvider { @@ -74,5 +108,6 @@ struct ContactsContainerView_Previews: PreviewProvider { viewModel: .init() ) .environment(\.managedObjectContext, CurrentApp.coreDataManager.mainContext) + .environmentObject(SampleData.appStore) } } diff --git a/day-079/Projects/QRConnections/QRConnections/Scenes/RootView.swift b/day-079/Projects/QRConnections/QRConnections/Scenes/RootView.swift index 2eba377..d561154 100644 --- a/day-079/Projects/QRConnections/QRConnections/Scenes/RootView.swift +++ b/day-079/Projects/QRConnections/QRConnections/Scenes/RootView.swift @@ -25,7 +25,6 @@ struct RootView: View { extension RootView { var body: some View { - TabView { ContactsContainerView(viewModel: .init()) .tabItem { @@ -42,7 +41,6 @@ extension RootView { } .tag(Tab.userQRCode) } -// .environmentObject(store) .edgesIgnoringSafeArea(.top) } }