Build out views and data flow for creating the users' QR code image.
This commit is contained in:
parent
de8ee275cb
commit
5d0c36cb09
|
@ -29,6 +29,17 @@
|
|||
F3203BC523C8714700265268 /* SampleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BC423C8714700265268 /* SampleData.swift */; };
|
||||
F3203BC723C8717F00265268 /* SampleData+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BC623C8717F00265268 /* SampleData+Contacts.swift */; };
|
||||
F3203BC923C871D300265268 /* PreviewDevice+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BC823C871D300265268 /* PreviewDevice+Utils.swift */; };
|
||||
F3203BCF23C9DAE100265268 /* UserQRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */; };
|
||||
F3203BD123C9DFDF00265268 /* ImageFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BD023C9DFDF00265268 /* ImageFilterService.swift */; };
|
||||
F3203BD323C9F20C00265268 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BD223C9F20C00265268 /* AppState.swift */; };
|
||||
F3203BD523C9F3E900265268 /* UserProfileState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BD423C9F3E900265268 /* UserProfileState.swift */; };
|
||||
F3203BE223CA31EB00265268 /* SampleData+AppStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BE123CA31EB00265268 /* SampleData+AppStore.swift */; };
|
||||
F3203BE523CA412600265268 /* Burritos in Frameworks */ = {isa = PBXBuildFile; productRef = F3203BE423CA412600265268 /* Burritos */; };
|
||||
F3203BEB23CC977000265268 /* UserQRCodeContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BEA23CC977000265268 /* UserQRCodeContainerView.swift */; };
|
||||
F3203BED23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BEC23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.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 */; };
|
||||
F3203BF423CD140100265268 /* ContactQRCodeRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -54,6 +65,16 @@
|
|||
F3203BC423C8714700265268 /* SampleData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleData.swift; sourceTree = "<group>"; };
|
||||
F3203BC623C8717F00265268 /* SampleData+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SampleData+Contacts.swift"; sourceTree = "<group>"; };
|
||||
F3203BC823C871D300265268 /* PreviewDevice+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreviewDevice+Utils.swift"; sourceTree = "<group>"; };
|
||||
F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserQRCodeView.swift; sourceTree = "<group>"; };
|
||||
F3203BD023C9DFDF00265268 /* ImageFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFilterService.swift; sourceTree = "<group>"; };
|
||||
F3203BD223C9F20C00265268 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||
F3203BD423C9F3E900265268 /* UserProfileState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileState.swift; sourceTree = "<group>"; };
|
||||
F3203BE123CA31EB00265268 /* SampleData+AppStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SampleData+AppStore.swift"; sourceTree = "<group>"; };
|
||||
F3203BEA23CC977000265268 /* UserQRCodeContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserQRCodeContainerView.swift; sourceTree = "<group>"; };
|
||||
F3203BEC23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserQRCodeContainerView+ViewModel.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>"; };
|
||||
F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactQRCodeRepresentable.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -63,6 +84,7 @@
|
|||
files = (
|
||||
F3203BA223C7632C00265268 /* CypherPoetSwiftUIKit in Frameworks */,
|
||||
F3203BA523C7634600265268 /* CypherPoetCoreDataKit in Frameworks */,
|
||||
F3203BE523CA412600265268 /* Burritos in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -144,6 +166,8 @@
|
|||
F3203B9B23C760C900265268 /* Reusables */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BF223CD13F300265268 /* Protocols */,
|
||||
F3203BD023C9DFDF00265268 /* ImageFilterService.swift */,
|
||||
);
|
||||
path = Reusables;
|
||||
sourceTree = "<group>";
|
||||
|
@ -159,6 +183,8 @@
|
|||
F3203BA823C82C8F00265268 /* State */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BD223C9F20C00265268 /* AppState.swift */,
|
||||
F3203BD423C9F3E900265268 /* UserProfileState.swift */,
|
||||
);
|
||||
path = State;
|
||||
sourceTree = "<group>";
|
||||
|
@ -174,6 +200,11 @@
|
|||
F3203BAC23C8337F00265268 /* User QR Code */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BCE23C9DAE000265268 /* UserQRCodeView.swift */,
|
||||
F3203BEA23CC977000265268 /* UserQRCodeContainerView.swift */,
|
||||
F3203BEE23CC985100265268 /* UserQRCodeFormView.swift */,
|
||||
F3203BEC23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift */,
|
||||
F3203BF023CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift */,
|
||||
);
|
||||
path = "User QR Code";
|
||||
sourceTree = "<group>";
|
||||
|
@ -205,10 +236,19 @@
|
|||
children = (
|
||||
F3203BC423C8714700265268 /* SampleData.swift */,
|
||||
F3203BC623C8717F00265268 /* SampleData+Contacts.swift */,
|
||||
F3203BE123CA31EB00265268 /* SampleData+AppStore.swift */,
|
||||
);
|
||||
path = SampleData;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F3203BF223CD13F300265268 /* Protocols */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BF323CD140100265268 /* ContactQRCodeRepresentable.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -228,6 +268,7 @@
|
|||
packageProductDependencies = (
|
||||
F3203BA123C7632C00265268 /* CypherPoetSwiftUIKit */,
|
||||
F3203BA423C7634600265268 /* CypherPoetCoreDataKit */,
|
||||
F3203BE423CA412600265268 /* Burritos */,
|
||||
);
|
||||
productName = QRConnections;
|
||||
productReference = F3203B7E23C7609500265268 /* QRConnections.app */;
|
||||
|
@ -260,6 +301,7 @@
|
|||
packageReferences = (
|
||||
F3203BA023C7632C00265268 /* XCRemoteSwiftPackageReference "CypherPoetSwiftUIKit" */,
|
||||
F3203BA323C7634600265268 /* XCRemoteSwiftPackageReference "CypherPoetCoreDataKit" */,
|
||||
F3203BE323CA412500265268 /* XCRemoteSwiftPackageReference "Burritos" */,
|
||||
);
|
||||
productRefGroup = F3203B7F23C7609500265268 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -295,15 +337,25 @@
|
|||
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 */,
|
||||
F3203BB123C8345400265268 /* RootView.swift in Sources */,
|
||||
F3203BC723C8717F00265268 /* SampleData+Contacts.swift in Sources */,
|
||||
F3203BD323C9F20C00265268 /* AppState.swift in Sources */,
|
||||
F3203BF423CD140100265268 /* ContactQRCodeRepresentable.swift in Sources */,
|
||||
F3203BB723C844F100265268 /* ContactsListView.swift in Sources */,
|
||||
F3203BED23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift in Sources */,
|
||||
F3203BB523C841A700265268 /* ContactsContainerView+ViewModel.swift in Sources */,
|
||||
F3203BEB23CC977000265268 /* UserQRCodeContainerView.swift in Sources */,
|
||||
F3203BF123CC9B1600265268 /* UserQRCodeFormView+ViewModel.swift in Sources */,
|
||||
F3203BEF23CC985100265268 /* UserQRCodeFormView.swift in Sources */,
|
||||
F3203BE223CA31EB00265268 /* SampleData+AppStore.swift in Sources */,
|
||||
F3203BD123C9DFDF00265268 /* ImageFilterService.swift in Sources */,
|
||||
F3203B8723C7609500265268 /* QRConnections.xcdatamodeld in Sources */,
|
||||
F3203BBD23C8579400265268 /* Contact+CoreDataClass.swift in Sources */,
|
||||
F3203BAA23C82CB100265268 /* CurrentApplication.swift in Sources */,
|
||||
F3203BC523C8714700265268 /* SampleData.swift in Sources */,
|
||||
F3203BD523C9F3E900265268 /* UserProfileState.swift in Sources */,
|
||||
F3203B8423C7609500265268 /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -516,6 +568,14 @@
|
|||
minimumVersion = 0.0.3;
|
||||
};
|
||||
};
|
||||
F3203BE323CA412500265268 /* XCRemoteSwiftPackageReference "Burritos" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/guillermomuntaner/Burritos.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.0.3;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
@ -529,6 +589,11 @@
|
|||
package = F3203BA323C7634600265268 /* XCRemoteSwiftPackageReference "CypherPoetCoreDataKit" */;
|
||||
productName = CypherPoetCoreDataKit;
|
||||
};
|
||||
F3203BE423CA412600265268 /* Burritos */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F3203BE323CA412500265268 /* XCRemoteSwiftPackageReference "Burritos" */;
|
||||
productName = Burritos;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "Burritos",
|
||||
"repositoryURL": "https://github.com/guillermomuntaner/Burritos.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "309dbe1b5b3af8839ca6a7ebb2ad6ddf041bf420",
|
||||
"version": "0.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "CypherPoetCoreDataKit",
|
||||
"repositoryURL": "https://github.com/CypherPoet/CypherPoetCoreDataKit.git",
|
||||
|
|
|
@ -12,9 +12,11 @@ import CypherPoetCoreDataKit_CoreDataManager
|
|||
|
||||
struct CurrentApplication {
|
||||
var coreDataManager: CoreDataManager
|
||||
var imageFilteringService: ImageFilterService
|
||||
}
|
||||
|
||||
|
||||
var CurrentApp = CurrentApplication(
|
||||
coreDataManager: .shared
|
||||
coreDataManager: .shared,
|
||||
imageFilteringService: .shared
|
||||
)
|
||||
|
|
|
@ -25,6 +25,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
// Use a UIHostingController as window root view controller.
|
||||
if let windowScene = scene as? UIWindowScene {
|
||||
let window = UIWindow(windowScene: windowScene)
|
||||
let appStore = AppStore(initialState: AppState(), appReducer: appReducer)
|
||||
|
||||
// Get the managed object context from the shared persistent container.
|
||||
let managedObjectContext = CurrentApp.coreDataManager.mainContext
|
||||
|
@ -37,6 +38,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
// Add `@Environment(\.managedObjectContext)` in the views that will need the context.
|
||||
let contentView = RootView()
|
||||
.environment(\.managedObjectContext, managedObjectContext)
|
||||
.environmentObject(appStore)
|
||||
|
||||
window.rootViewController = UIHostingController(rootView: contentView)
|
||||
|
||||
|
|
|
@ -11,15 +11,18 @@ import Foundation
|
|||
import CoreData
|
||||
|
||||
|
||||
extension Contact {
|
||||
@NSManaged public var uuid: UUID?
|
||||
@NSManaged public var name: String?
|
||||
extension Contact: ContactQRCodeRepresentable {
|
||||
@NSManaged public var qrCodeData: Data?
|
||||
|
||||
@NSManaged public var statusValue: Int16
|
||||
|
||||
|
||||
var status: Status {
|
||||
get { Contact.Status(rawValue: statusValue)! }
|
||||
set { statusValue = newValue.rawValue }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ContactQRCodeRepresentable
|
||||
@NSManaged public var name: String
|
||||
@NSManaged public var uuid: UUID
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="15702" systemVersion="19C57" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Contact" representedClassName="Contact" syncable="YES">
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="qrCodeData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="statusValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="uuid" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="uuid"/>
|
||||
|
@ -11,6 +12,6 @@
|
|||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Contact" positionX="-63" positionY="-18" width="128" height="88"/>
|
||||
<element name="Contact" positionX="6.3984375" positionY="-108.1640625" width="128" height="103"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// AppState.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/11/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import CypherPoetSwiftUIKit_DataFlowUtils
|
||||
|
||||
|
||||
struct AppState {
|
||||
var userProfileState = UserProfileState()
|
||||
}
|
||||
|
||||
|
||||
|
||||
//enum AppSideEffect: SideEffect {}
|
||||
|
||||
|
||||
|
||||
enum AppAction {
|
||||
case userProfile(_ action: UserProfileAction)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Reducer
|
||||
let appReducer = Reducer<AppState, AppAction> { appState, action in
|
||||
switch action {
|
||||
case .userProfile(let action):
|
||||
userProfileReducer.reduce(&appState.userProfileState, action)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typealias AppStore = Store<AppState, AppAction>
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// UserProfileState.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/11/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import CypherPoetSwiftUIKit_DataFlowUtils
|
||||
import UserDefault
|
||||
import Combine
|
||||
import CoreImage.CIFilterBuiltins
|
||||
import UIKit
|
||||
|
||||
|
||||
struct UserProfileState: ContactQRCodeRepresentable {
|
||||
|
||||
@UserDefault("user-profile-qr-code-data", defaultValue: Data())
|
||||
var qrCodeData: Data
|
||||
|
||||
@UserDefault("user-profile-uuid-string", defaultValue: UUID().uuidString)
|
||||
var uuidString: String
|
||||
|
||||
@UserDefault("user-profile-name", defaultValue: "")
|
||||
var name: String
|
||||
|
||||
|
||||
var uuid: UUID {
|
||||
guard let uuid = UUID(uuidString: uuidString) else {
|
||||
preconditionFailure("Unable to make UUID from stored `uuidString`")
|
||||
}
|
||||
|
||||
return uuid
|
||||
}
|
||||
|
||||
var qrCodeCGImage: CGImage? {
|
||||
didSet {
|
||||
guard let cgImage = qrCodeCGImage else { return }
|
||||
self.qrCodeData = UIImage(cgImage: cgImage).pngData() ?? Data()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum UserProfileSideEffect: SideEffect {
|
||||
case generateQRCode(data: Data)
|
||||
|
||||
|
||||
func mapToAction() -> AnyPublisher<AppAction, Never> {
|
||||
switch self {
|
||||
case .generateQRCode(let data):
|
||||
return Just(data)
|
||||
.flatMap { data in
|
||||
CurrentApp.imageFilteringService.createCGImage(
|
||||
from: CIFilter.qrCodeGenerator(),
|
||||
withAttributes: [
|
||||
"inputMessage": data
|
||||
]
|
||||
)
|
||||
.map { AppAction.userProfile(.qrCodeImageSet($0)) }
|
||||
.catch { _ in Just(AppAction.userProfile(.qrCodeDataSet(Data()))) }
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum UserProfileAction {
|
||||
case qrCodeImageSet(CGImage)
|
||||
case qrCodeDataSet(Data)
|
||||
case userNameSet(String)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Reducer
|
||||
let userProfileReducer: Reducer<UserProfileState, UserProfileAction> = Reducer(
|
||||
reduce: { state, action in
|
||||
switch action {
|
||||
case .qrCodeDataSet(let data):
|
||||
state.qrCodeData = data
|
||||
case .qrCodeImageSet(let cgImage):
|
||||
state.qrCodeCGImage = cgImage
|
||||
case .userNameSet(let name):
|
||||
state.name = name
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// SampleData+AppStore.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/11/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension SampleData {
|
||||
|
||||
static let userProfileState = UserProfileState()
|
||||
|
||||
|
||||
static let appStore = AppStore(
|
||||
initialState: AppState(
|
||||
userProfileState: SampleData.userProfileState
|
||||
),
|
||||
appReducer: appReducer
|
||||
)
|
||||
}
|
|
@ -11,7 +11,6 @@ import CoreData
|
|||
|
||||
|
||||
extension SampleData {
|
||||
|
||||
static let contactUUIDs: [UUID] = [
|
||||
UUID(uuidString: "879da3b9-4f3b-459f-8b53-2e39908e1900")!,
|
||||
UUID(uuidString: "23bb3482-3655-4292-ae90-223c3ac6fa69")!,
|
||||
|
@ -19,14 +18,26 @@ extension SampleData {
|
|||
UUID(uuidString: "c3edfa5c-3278-4f45-b86e-c272ba787a70")!,
|
||||
UUID(uuidString: "611132c1-2276-435d-b7fd-5e1f54e6f203")!,
|
||||
]
|
||||
// static let contactUUIDStrings: [UUID] = [
|
||||
// "879da3b9-4f3b-459f-8b53-2e39908e1900",
|
||||
// "23bb3482-3655-4292-ae90-223c3ac6fa69",
|
||||
// "015505c4-2732-48e3-a697-0c45ec1a4ccf",
|
||||
// "c3edfa5c-3278-4f45-b86e-c272ba787a70",
|
||||
// "611132c1-2276-435d-b7fd-5e1f54e6f203",
|
||||
// ]
|
||||
|
||||
static func makeContacts(in context: NSManagedObjectContext) -> [Contact] {
|
||||
contactUUIDs.enumerated().map { (index, uuid) in
|
||||
let contact = Contact(context: context)
|
||||
// let contactInfo = ContactInfo(context: context)
|
||||
|
||||
contact.status = Contact.Status.allCases.randomElement()!
|
||||
contact.uuid = uuid
|
||||
contact.name = "Contact \(index + 1)"
|
||||
contact.status = Contact.Status.allCases.randomElement()!
|
||||
|
||||
// contactInfo.uuid = uuid
|
||||
// contactInfo.name = "Contact \(index + 1)"
|
||||
// contactInfo.contact = contact
|
||||
|
||||
return contact
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import Foundation
|
||||
import CoreImage
|
||||
import CoreImage.CIFilterBuiltins
|
||||
import Combine
|
||||
import UIKit
|
||||
|
||||
|
||||
|
||||
public final class ImageFilterService {
|
||||
public typealias FilterAtrributes = [String: Any]
|
||||
|
||||
public enum Error: Swift.Error {
|
||||
case cgImage(_ message: String)
|
||||
case ciImage(_ message: String)
|
||||
case filtering(_ message: String)
|
||||
}
|
||||
|
||||
// 🔑 Creating a CIContext is expensive, so we'll create it once and reuse it throughout the app.
|
||||
lazy private var context = CIContext()
|
||||
|
||||
|
||||
public let filteringQueue: DispatchQueue
|
||||
|
||||
|
||||
public init(
|
||||
queue filteringQueue: DispatchQueue = DispatchQueue(
|
||||
label: "Image Filtering Service",
|
||||
qos: .userInitiated
|
||||
)
|
||||
) {
|
||||
self.filteringQueue = filteringQueue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ImageFilterService {
|
||||
|
||||
public func createCGImage(
|
||||
from filter: CIFilter,
|
||||
withAttributes filterAtrributes: FilterAtrributes = [:]
|
||||
) -> AnyPublisher<CGImage, ImageFilterService.Error> {
|
||||
createCIImage(byApplying: filter, withAttributes: filterAtrributes)
|
||||
.flatMap(createCGImage(from:))
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
private func createCGImage(
|
||||
from filteredImage: CIImage
|
||||
) -> AnyPublisher<CGImage, ImageFilterService.Error> {
|
||||
Just(filteredImage)
|
||||
.print("createCGImage")
|
||||
.tryMap { filteredImage -> CGImage in
|
||||
guard
|
||||
let cgImage = self.context.createCGImage(filteredImage, from: filteredImage.extent)
|
||||
else {
|
||||
print("createCGImage failed")
|
||||
throw Error.cgImage("Failed to create cgImage from filtered ciImage")
|
||||
}
|
||||
|
||||
return cgImage
|
||||
}
|
||||
.mapError( { $0 as! ImageFilterService.Error })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
private func createCIImage(
|
||||
byApplying filter: CIFilter,
|
||||
withAttributes filterAtrributes: FilterAtrributes = [:]
|
||||
) -> AnyPublisher<CIImage, ImageFilterService.Error> {
|
||||
Just(filter)
|
||||
.tryMap { filter in
|
||||
for (key, value) in filterAtrributes {
|
||||
filter.setValue(value, forKey: key)
|
||||
}
|
||||
|
||||
guard let filteredImage = filter.outputImage else {
|
||||
throw Error.filtering("Failed to output image during filtering")
|
||||
}
|
||||
|
||||
return filteredImage
|
||||
}
|
||||
.mapError( { $0 as! ImageFilterService.Error })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ImageFilterService {
|
||||
public static let shared = ImageFilterService()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// ContactQRCodeRepresentable.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/13/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public protocol ContactQRCodeRepresentable {
|
||||
var uuid: UUID { get }
|
||||
var name: String { get }
|
||||
}
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
|||
|
||||
|
||||
struct ContactsContainerView: View {
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
@ObservedObject var viewModel = ViewModel()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ extension ContactsListView {
|
|||
|
||||
var body: some View {
|
||||
List(contacts) { contact in
|
||||
Text(contact.name ?? "No Name")
|
||||
Text(contact.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import SwiftUI
|
|||
|
||||
|
||||
struct RootView: View {
|
||||
@EnvironmentObject private var store: AppStore
|
||||
|
||||
enum Tab {
|
||||
case collectedContacts
|
||||
case userQRCode
|
||||
|
@ -32,13 +34,15 @@ extension RootView {
|
|||
}
|
||||
.tag(Tab.collectedContacts)
|
||||
|
||||
Text("")
|
||||
|
||||
UserQRCodeContainerView(viewModel: .init(userProfileState: userProfileState))
|
||||
.tabItem {
|
||||
Image(systemName: "qrcode")
|
||||
Text("My Alias")
|
||||
Text("Your QR")
|
||||
}
|
||||
.tag(Tab.userQRCode)
|
||||
}
|
||||
// .environmentObject(store)
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +50,7 @@ extension RootView {
|
|||
|
||||
// MARK: - Computeds
|
||||
extension RootView {
|
||||
|
||||
|
||||
var userProfileState: UserProfileState { store.state.userProfileState }
|
||||
}
|
||||
|
||||
|
||||
|
@ -64,5 +67,6 @@ struct RootView_Previews: PreviewProvider {
|
|||
|
||||
static var previews: some View {
|
||||
RootView()
|
||||
.environmentObject(SampleData.appStore)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// UserQRCodeContainerView+ViewModel.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/13/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
|
||||
extension UserQRCodeContainerView {
|
||||
final class ViewModel: ObservableObject {
|
||||
private var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
var userProfileState: UserProfileState
|
||||
|
||||
|
||||
// MARK: - Published Outputs
|
||||
@Published var qrCodeCGImage: CGImage?
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
init(
|
||||
userProfileState: UserProfileState
|
||||
) {
|
||||
self.userProfileState = userProfileState
|
||||
self.qrCodeCGImage = userProfileState.qrCodeCGImage
|
||||
|
||||
setupSubscribers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Publishers
|
||||
extension UserQRCodeContainerView.ViewModel {
|
||||
|
||||
private var userProfileStatePublisher: Publishers.Share<AnyPublisher<UserProfileState, Never>> {
|
||||
CurrentValueSubject(userProfileState)
|
||||
.eraseToAnyPublisher()
|
||||
.share()
|
||||
}
|
||||
|
||||
private var qrCodeCGImagePublisher: AnyPublisher<CGImage?, Never> {
|
||||
userProfileStatePublisher
|
||||
.map(\.qrCodeCGImage)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension UserQRCodeContainerView.ViewModel {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public Methods
|
||||
extension UserQRCodeContainerView.ViewModel {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension UserQRCodeContainerView.ViewModel {
|
||||
|
||||
func setupSubscribers() {
|
||||
qrCodeCGImagePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.qrCodeCGImage, on: self)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// UserQRCodeContainerView.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/13/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
|
||||
struct UserQRCodeContainerView {
|
||||
@EnvironmentObject private var store: AppStore
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
}
|
||||
|
||||
|
||||
// MARK: - View
|
||||
extension UserQRCodeContainerView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
if viewModel.qrCodeCGImage != nil {
|
||||
UserQRCodeView(cgImage: viewModel.qrCodeCGImage!)
|
||||
.transition(
|
||||
AnyTransition
|
||||
.move(edge: .top)
|
||||
.animation(Animation.easeOut(duration: 0.4))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
UserQRCodeFormView(
|
||||
viewModel: .init(userProfileState: viewModel.userProfileState)
|
||||
)
|
||||
}
|
||||
.navigationBarTitle("Your QR")
|
||||
}
|
||||
.onAppear {
|
||||
let qrCodeData = self.viewModel.userProfileState.qrCodeData
|
||||
|
||||
if self.viewModel.qrCodeCGImage == nil && !qrCodeData.isEmpty {
|
||||
self.store.send(UserProfileSideEffect.generateQRCode(data: qrCodeData))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension UserQRCodeContainerView {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - View Variables
|
||||
extension UserQRCodeContainerView {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension UserQRCodeContainerView {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Preview
|
||||
struct UserQRCodeContainerView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
let store = SampleData.appStore
|
||||
|
||||
return UserQRCodeContainerView(
|
||||
viewModel: .init(userProfileState: store.state.userProfileState)
|
||||
)
|
||||
.environmentObject(store)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// UserQRCodeFormView+ViewModel.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/13/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
|
||||
extension UserQRCodeFormView {
|
||||
final class ViewModel: ObservableObject {
|
||||
private var subscriptions = Set<AnyCancellable>()
|
||||
private var userProfileState: UserProfileState
|
||||
|
||||
|
||||
// MARK: - Form Inputs
|
||||
@Published var userNameText: String = ""
|
||||
|
||||
|
||||
// MARK: - Published Outputs
|
||||
@Published var userName: String = ""
|
||||
@Published var qrCodeData = Data()
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
init(
|
||||
userProfileState: UserProfileState
|
||||
) {
|
||||
self.userProfileState = userProfileState
|
||||
self.userNameText = userProfileState.name
|
||||
|
||||
setupSubscribers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Publishers
|
||||
extension UserQRCodeFormView.ViewModel {
|
||||
|
||||
private var userProfileStatePublisher: Publishers.Share<AnyPublisher<UserProfileState, Never>> {
|
||||
CurrentValueSubject(userProfileState)
|
||||
.eraseToAnyPublisher()
|
||||
.share()
|
||||
}
|
||||
|
||||
|
||||
private var userNameTextPublisher: AnyPublisher<String, Never> {
|
||||
$userNameText
|
||||
.dropFirst(1)
|
||||
.debounce(for: .milliseconds(650), scheduler: DispatchQueue.main)
|
||||
.removeDuplicates()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
private var userNamePublisher: Publishers.Share<AnyPublisher<String, Never>> {
|
||||
userNameTextPublisher
|
||||
.share()
|
||||
}
|
||||
|
||||
|
||||
private var qrCodeDataPublisher: AnyPublisher<Data, Never> {
|
||||
userNamePublisher.share()
|
||||
// .print("qrCodeDataPublisher")
|
||||
.map { text in
|
||||
text
|
||||
.appending(self.userProfileState.uuid.uuidString)
|
||||
.data(using: .utf8)!
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension UserQRCodeFormView.ViewModel {}
|
||||
|
||||
|
||||
// MARK: - Public Methods
|
||||
extension UserQRCodeFormView.ViewModel {}
|
||||
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension UserQRCodeFormView.ViewModel {
|
||||
func setupSubscribers() {
|
||||
qrCodeDataPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.qrCodeData, on: self)
|
||||
.store(in: &subscriptions)
|
||||
|
||||
userNamePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.print("userNamePublisher - assigning")
|
||||
.assign(to: \.userName, on: self)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// UserQRCodeFormView.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/13/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct UserQRCodeFormView: View {
|
||||
@EnvironmentObject private var store: AppStore
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Body
|
||||
extension UserQRCodeFormView {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
TextField("Name", text: $viewModel.userNameText)
|
||||
}
|
||||
}
|
||||
.onReceive(viewModel.$userName.dropFirst(1)) { name in
|
||||
self.store.send(.userProfile(.userNameSet(name)))
|
||||
}
|
||||
.onReceive(viewModel.$qrCodeData.dropFirst(1)) { data in
|
||||
self.store.send(UserProfileSideEffect.generateQRCode(data: data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension UserQRCodeFormView {}
|
||||
|
||||
|
||||
// MARK: - View Variables
|
||||
extension UserQRCodeFormView {}
|
||||
|
||||
|
||||
|
||||
// MARK: - Preview
|
||||
struct UserQRCodeFormView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
let store = SampleData.appStore
|
||||
|
||||
return UserQRCodeFormView(viewModel: .init(userProfileState: store.state.userProfileState))
|
||||
.environmentObject(store)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
//
|
||||
// UserQRCodeView.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/11/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct UserQRCodeView {
|
||||
let cgImage: CGImage
|
||||
}
|
||||
|
||||
|
||||
// MARK: - View
|
||||
extension UserQRCodeView: View {
|
||||
|
||||
var body: some View {
|
||||
Image(uiImage: .init(cgImage: cgImage))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension UserQRCodeView {}
|
||||
|
||||
|
||||
// MARK: - View Variables
|
||||
extension UserQRCodeView {}
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension UserQRCodeView {
|
||||
}
|
||||
|
||||
|
||||
|
||||
//// MARK: - Preview
|
||||
//struct UserQRCodeView_Previews: PreviewProvider {
|
||||
//
|
||||
// static var previews: some View {
|
||||
// UserQRCodeView(cgImage: )
|
||||
// }
|
||||
//}
|
Loading…
Reference in New Issue