Implement ScannerViewController and new Contact creation.
This commit is contained in:
parent
2546be8bcb
commit
43baab40d4
|
@ -40,6 +40,11 @@
|
||||||
F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */; };
|
F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */; };
|
||||||
F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.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 */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
@ -75,6 +80,11 @@
|
||||||
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserQRCodeFormView.swift; sourceTree = "<group>"; };
|
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserQRCodeFormView.swift; sourceTree = "<group>"; };
|
||||||
F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserQRCodeFormView+ViewModel.swift"; sourceTree = "<group>"; };
|
F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserQRCodeFormView+ViewModel.swift"; sourceTree = "<group>"; };
|
||||||
F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactQRCodeRepresentable.swift; sourceTree = "<group>"; };
|
F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactQRCodeRepresentable.swift; sourceTree = "<group>"; };
|
||||||
|
F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodeScannerView+Coordinator.swift"; sourceTree = "<group>"; };
|
||||||
|
F3203BFA23CD430500265268 /* QRCodeScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerView.swift; sourceTree = "<group>"; };
|
||||||
|
F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannerViewControlller.swift; sourceTree = "<group>"; };
|
||||||
|
F326140723CE3928009EC215 /* ContactsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsState.swift; sourceTree = "<group>"; };
|
||||||
|
F326140923CE689A009EC215 /* Contact+InitHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Contact+InitHelpers.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -134,10 +144,10 @@
|
||||||
F3203B9823C760BF00265268 /* App */ = {
|
F3203B9823C760BF00265268 /* App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F3203B9D23C7610400265268 /* LaunchScreen.storyboard */,
|
|
||||||
F3203B8323C7609500265268 /* SceneDelegate.swift */,
|
|
||||||
F3203B8123C7609500265268 /* AppDelegate.swift */,
|
F3203B8123C7609500265268 /* AppDelegate.swift */,
|
||||||
F3203BA923C82CB100265268 /* CurrentApplication.swift */,
|
F3203BA923C82CB100265268 /* CurrentApplication.swift */,
|
||||||
|
F3203B8323C7609500265268 /* SceneDelegate.swift */,
|
||||||
|
F3203B9D23C7610400265268 /* LaunchScreen.storyboard */,
|
||||||
);
|
);
|
||||||
path = App;
|
path = App;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -145,9 +155,9 @@
|
||||||
F3203B9923C760C300265268 /* Scenes */ = {
|
F3203B9923C760C300265268 /* Scenes */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
F3203BB023C8345400265268 /* RootView.swift */,
|
||||||
F3203BAD23C8339400265268 /* Collected Contacts */,
|
F3203BAD23C8339400265268 /* Collected Contacts */,
|
||||||
F3203BAC23C8337F00265268 /* User QR Code */,
|
F3203BAC23C8337F00265268 /* User QR Code */,
|
||||||
F3203BB023C8345400265268 /* RootView.swift */,
|
|
||||||
);
|
);
|
||||||
path = Scenes;
|
path = Scenes;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -155,10 +165,10 @@
|
||||||
F3203B9A23C760C700265268 /* Data */ = {
|
F3203B9A23C760C700265268 /* Data */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F3203BAB23C831E300265268 /* Models */,
|
|
||||||
F3203BA823C82C8F00265268 /* State */,
|
|
||||||
F3203B8523C7609500265268 /* QRConnections.xcdatamodeld */,
|
|
||||||
F3203BA623C7636600265268 /* CoreDataManager+Utils.swift */,
|
F3203BA623C7636600265268 /* CoreDataManager+Utils.swift */,
|
||||||
|
F3203BAB23C831E300265268 /* Models */,
|
||||||
|
F3203B8523C7609500265268 /* QRConnections.xcdatamodeld */,
|
||||||
|
F3203BA823C82C8F00265268 /* State */,
|
||||||
);
|
);
|
||||||
path = Data;
|
path = Data;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -166,8 +176,9 @@
|
||||||
F3203B9B23C760C900265268 /* Reusables */ = {
|
F3203B9B23C760C900265268 /* Reusables */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F3203BF223CD13F300265268 /* Protocols */,
|
F3203BF823CD42F400265268 /* UIKit Wrappers */,
|
||||||
F3203BD023C9DFDF00265268 /* ImageFilterService.swift */,
|
F3203BD023C9DFDF00265268 /* ImageFilterService.swift */,
|
||||||
|
F3203BF223CD13F300265268 /* Protocols */,
|
||||||
);
|
);
|
||||||
path = Reusables;
|
path = Reusables;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -185,6 +196,7 @@
|
||||||
children = (
|
children = (
|
||||||
F3203BD223C9F20C00265268 /* AppState.swift */,
|
F3203BD223C9F20C00265268 /* AppState.swift */,
|
||||||
F3203BD423C9F3E900265268 /* UserProfileState.swift */,
|
F3203BD423C9F3E900265268 /* UserProfileState.swift */,
|
||||||
|
F326140723CE3928009EC215 /* ContactsState.swift */,
|
||||||
);
|
);
|
||||||
path = State;
|
path = State;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -200,11 +212,11 @@
|
||||||
F3203BAC23C8337F00265268 /* User QR Code */ = {
|
F3203BAC23C8337F00265268 /* User QR Code */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */,
|
|
||||||
F3203BEA23CC977000265268 /* UserQRCodeContainerView.swift */,
|
F3203BEA23CC977000265268 /* UserQRCodeContainerView.swift */,
|
||||||
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */,
|
|
||||||
F3203BEC23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift */,
|
F3203BEC23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift */,
|
||||||
|
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */,
|
||||||
F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */,
|
F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */,
|
||||||
|
F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */,
|
||||||
);
|
);
|
||||||
path = "User QR Code";
|
path = "User QR Code";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -213,8 +225,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F3203BB223C8368800265268 /* ContactsContainerView.swift */,
|
F3203BB223C8368800265268 /* ContactsContainerView.swift */,
|
||||||
F3203BB623C844F100265268 /* ContactsListView.swift */,
|
|
||||||
F3203BB423C841A700265268 /* ContactsContainerView+ViewModel.swift */,
|
F3203BB423C841A700265268 /* ContactsContainerView+ViewModel.swift */,
|
||||||
|
F3203BB623C844F100265268 /* ContactsListView.swift */,
|
||||||
);
|
);
|
||||||
path = "Collected Contacts";
|
path = "Collected Contacts";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -225,6 +237,7 @@
|
||||||
F3203BB823C8467000265268 /* Contact+FilterState.swift */,
|
F3203BB823C8467000265268 /* Contact+FilterState.swift */,
|
||||||
F3203BBB23C8579400265268 /* Contact+CoreDataClass.swift */,
|
F3203BBB23C8579400265268 /* Contact+CoreDataClass.swift */,
|
||||||
F3203BBC23C8579400265268 /* Contact+CoreDataProperties.swift */,
|
F3203BBC23C8579400265268 /* Contact+CoreDataProperties.swift */,
|
||||||
|
F326140923CE689A009EC215 /* Contact+InitHelpers.swift */,
|
||||||
F3203BC123C8588E00265268 /* Contact+FetchHelpers.swift */,
|
F3203BC123C8588E00265268 /* Contact+FetchHelpers.swift */,
|
||||||
F3203BBF23C8584500265268 /* Contact+Status.swift */,
|
F3203BBF23C8584500265268 /* Contact+Status.swift */,
|
||||||
);
|
);
|
||||||
|
@ -249,6 +262,24 @@
|
||||||
path = Protocols;
|
path = Protocols;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
F3203BF823CD42F400265268 /* UIKit Wrappers */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F3203BFD23CDF68D00265268 /* Scanner View */,
|
||||||
|
);
|
||||||
|
path = "UIKit Wrappers";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F3203BFD23CDF68D00265268 /* Scanner View */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F3203BF923CD430500265268 /* QRCodeScannerView+Coordinator.swift */,
|
||||||
|
F3203BFA23CD430500265268 /* QRCodeScannerView.swift */,
|
||||||
|
F3203BFE23CDF6D100265268 /* ScannerViewControlller.swift */,
|
||||||
|
);
|
||||||
|
path = "Scanner View";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
@ -334,11 +365,13 @@
|
||||||
F3203BB323C8368800265268 /* ContactsContainerView.swift in Sources */,
|
F3203BB323C8368800265268 /* ContactsContainerView.swift in Sources */,
|
||||||
F3203BC923C871D300265268 /* PreviewDevice+Utils.swift in Sources */,
|
F3203BC923C871D300265268 /* PreviewDevice+Utils.swift in Sources */,
|
||||||
F3203BB923C8467000265268 /* Contact+FilterState.swift in Sources */,
|
F3203BB923C8467000265268 /* Contact+FilterState.swift in Sources */,
|
||||||
|
F3203BFB23CD430500265268 /* QRCodeScannerView+Coordinator.swift in Sources */,
|
||||||
F3203BC223C8588F00265268 /* Contact+FetchHelpers.swift in Sources */,
|
F3203BC223C8588F00265268 /* Contact+FetchHelpers.swift in Sources */,
|
||||||
F3203BC023C8584500265268 /* Contact+Status.swift in Sources */,
|
F3203BC023C8584500265268 /* Contact+Status.swift in Sources */,
|
||||||
F3203BA723C7636600265268 /* CoreDataManager+Utils.swift in Sources */,
|
F3203BA723C7636600265268 /* CoreDataManager+Utils.swift in Sources */,
|
||||||
F3203BCF23C9DAE100265268 /* UserQRCodeView.swift in Sources */,
|
F3203BCF23C9DAE100265268 /* UserQRCodeView.swift in Sources */,
|
||||||
F3203B8223C7609500265268 /* AppDelegate.swift in Sources */,
|
F3203B8223C7609500265268 /* AppDelegate.swift in Sources */,
|
||||||
|
F326140823CE3928009EC215 /* ContactsState.swift in Sources */,
|
||||||
F3203BB123C8345400265268 /* RootView.swift in Sources */,
|
F3203BB123C8345400265268 /* RootView.swift in Sources */,
|
||||||
F3203BC723C8717F00265268 /* SampleData+Contacts.swift in Sources */,
|
F3203BC723C8717F00265268 /* SampleData+Contacts.swift in Sources */,
|
||||||
F3203BD323C9F20C00265268 /* AppState.swift in Sources */,
|
F3203BD323C9F20C00265268 /* AppState.swift in Sources */,
|
||||||
|
@ -346,9 +379,11 @@
|
||||||
F3203BB723C844F100265268 /* ContactsListView.swift in Sources */,
|
F3203BB723C844F100265268 /* ContactsListView.swift in Sources */,
|
||||||
F3203BED23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift in Sources */,
|
F3203BED23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift in Sources */,
|
||||||
F3203BB523C841A700265268 /* ContactsContainerView+ViewModel.swift in Sources */,
|
F3203BB523C841A700265268 /* ContactsContainerView+ViewModel.swift in Sources */,
|
||||||
|
F326140A23CE689A009EC215 /* Contact+InitHelpers.swift in Sources */,
|
||||||
F3203BEB23CC977000265268 /* UserQRCodeContainerView.swift in Sources */,
|
F3203BEB23CC977000265268 /* UserQRCodeContainerView.swift in Sources */,
|
||||||
F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */,
|
F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */,
|
||||||
F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */,
|
F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */,
|
||||||
|
F3203BFF23CDF6D100265268 /* ScannerViewControlller.swift in Sources */,
|
||||||
F3203BE223CA31EB00265268 /* SampleData+AppStore.swift in Sources */,
|
F3203BE223CA31EB00265268 /* SampleData+AppStore.swift in Sources */,
|
||||||
F3203BD123C9DFDF00265268 /* ImageFilterService.swift in Sources */,
|
F3203BD123C9DFDF00265268 /* ImageFilterService.swift in Sources */,
|
||||||
F3203B8723C7609500265268 /* QRConnections.xcdatamodeld in Sources */,
|
F3203B8723C7609500265268 /* QRConnections.xcdatamodeld in Sources */,
|
||||||
|
@ -357,6 +392,7 @@
|
||||||
F3203BC523C8714700265268 /* SampleData.swift in Sources */,
|
F3203BC523C8714700265268 /* SampleData.swift in Sources */,
|
||||||
F3203BD523C9F3E900265268 /* UserProfileState.swift in Sources */,
|
F3203BD523C9F3E900265268 /* UserProfileState.swift in Sources */,
|
||||||
F3203B8423C7609500265268 /* SceneDelegate.swift in Sources */,
|
F3203B8423C7609500265268 /* SceneDelegate.swift in Sources */,
|
||||||
|
F3203BFC23CD430500265268 /* QRCodeScannerView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
"repositoryURL": "https://github.com/CypherPoet/CypherPoetCoreDataKit.git",
|
"repositoryURL": "https://github.com/CypherPoet/CypherPoetCoreDataKit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "5400fc3983f4174cd36cc79b24b7ef54affecf6b",
|
"revision": "e14a3abf3e679d41c9e0fcc55d3e4d7ccd8ce1b6",
|
||||||
"version": "0.0.5"
|
"version": "0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import CypherPoetSwiftUIKit_DataFlowUtils
|
||||||
|
|
||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
|
var contactsState = ContactsState()
|
||||||
var userProfileState = UserProfileState()
|
var userProfileState = UserProfileState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ struct AppState {
|
||||||
|
|
||||||
|
|
||||||
enum AppAction {
|
enum AppAction {
|
||||||
|
case contacts(_ action: ContactsAction)
|
||||||
case userProfile(_ action: UserProfileAction)
|
case userProfile(_ action: UserProfileAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +33,8 @@ let appReducer = Reducer<AppState, AppAction> { appState, action in
|
||||||
switch action {
|
switch action {
|
||||||
case .userProfile(let action):
|
case .userProfile(let action):
|
||||||
userProfileReducer.reduce(&appState.userProfileState, action)
|
userProfileReducer.reduce(&appState.userProfileState, action)
|
||||||
|
case .contacts(let action):
|
||||||
|
contactsReducer.reduce(&appState.contactsState, action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<AppAction, Never> {
|
||||||
|
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<ContactsState, ContactsAction> = Reducer(
|
||||||
|
reduce: { state, action in
|
||||||
|
switch action {
|
||||||
|
case .saveErrorMessageSet(let message):
|
||||||
|
state.saveErrorMessage = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -49,6 +49,8 @@
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>This app would like to scan QR codes in order to add to your collection of contacts.</string>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, ScanError>
|
||||||
|
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<QRCodeScannerView>
|
||||||
|
) -> UIViewControllerType {
|
||||||
|
let viewController = ScannerViewController()
|
||||||
|
|
||||||
|
viewController.codeTypes = codeTypes
|
||||||
|
viewController.captureSessionDelegate = context.coordinator
|
||||||
|
|
||||||
|
return viewController
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func updateUIViewController(
|
||||||
|
_ uiViewController: UIViewControllerType,
|
||||||
|
context: UIViewControllerRepresentableContext<QRCodeScannerView>
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Preview
|
||||||
|
struct QRCodeScannerView_Previews: PreviewProvider {
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
QRCodeScannerView(onScanCompleted: { _ in })
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<UITouch>, 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
|
|
@ -9,13 +9,17 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
struct ContactsContainerView: View {
|
struct ContactsContainerView {
|
||||||
|
@EnvironmentObject private var store: AppStore
|
||||||
|
|
||||||
@ObservedObject var viewModel = ViewModel()
|
@ObservedObject var viewModel = ViewModel()
|
||||||
|
|
||||||
|
@State private var isShowingScannerView = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - View
|
||||||
extension ContactsContainerView {
|
extension ContactsContainerView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
|
@ -27,6 +31,12 @@ extension ContactsContainerView {
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Collected Contacts")
|
.navigationBarTitle("Collected Contacts")
|
||||||
.navigationBarItems(trailing: addContactButton)
|
.navigationBarItems(trailing: addContactButton)
|
||||||
|
.sheet(isPresented: $isShowingScannerView) {
|
||||||
|
QRCodeScannerView(
|
||||||
|
simulatedData: "🚀 Rocket Man",
|
||||||
|
onScanCompleted: self.codeScanCompleted(_:)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +66,7 @@ extension ContactsContainerView {
|
||||||
|
|
||||||
private var addContactButton: some View {
|
private var addContactButton: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
self.isShowingScannerView = true
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: "qrcode.viewfinder")
|
Image(systemName: "qrcode.viewfinder")
|
||||||
.imageScale(.large)
|
.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
|
// MARK: - Preview
|
||||||
struct ContactsContainerView_Previews: PreviewProvider {
|
struct ContactsContainerView_Previews: PreviewProvider {
|
||||||
|
|
||||||
|
@ -74,5 +108,6 @@ struct ContactsContainerView_Previews: PreviewProvider {
|
||||||
viewModel: .init()
|
viewModel: .init()
|
||||||
)
|
)
|
||||||
.environment(\.managedObjectContext, CurrentApp.coreDataManager.mainContext)
|
.environment(\.managedObjectContext, CurrentApp.coreDataManager.mainContext)
|
||||||
|
.environmentObject(SampleData.appStore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ struct RootView: View {
|
||||||
extension RootView {
|
extension RootView {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
TabView {
|
TabView {
|
||||||
ContactsContainerView(viewModel: .init())
|
ContactsContainerView(viewModel: .init())
|
||||||
.tabItem {
|
.tabItem {
|
||||||
|
@ -42,7 +41,6 @@ extension RootView {
|
||||||
}
|
}
|
||||||
.tag(Tab.userQRCode)
|
.tag(Tab.userQRCode)
|
||||||
}
|
}
|
||||||
// .environmentObject(store)
|
|
||||||
.edgesIgnoringSafeArea(.top)
|
.edgesIgnoringSafeArea(.top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue