diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift index 80fbcc81f5d01ff10dac71a6f2bc831f4d4f91dd..6624bb2c6ef01482052069fdfa647d3269f302a8 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift @@ -24,6 +24,7 @@ public struct ContactState: Equatable { public enum ContactAction: Equatable { case start + case dbContactFetched(XXModels.Contact?) } public struct ContactEnvironment { @@ -58,8 +59,21 @@ extension ContactEnvironment { public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironment> { state, action, env in + enum DBFetchEffectID {} + switch action { case .start: + return try! env.db().fetchContactsPublisher(.init(id: [state.id])) + .assertNoFailure() + .map(\.first) + .map(ContactAction.dbContactFetched) + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + .cancellable(id: DBFetchEffectID.self, cancelInFlight: true) + + case .dbContactFetched(let contact): + state.dbContact = contact return .none } } diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift index a050747e31f2a0ee861bd9e8e17cf93e2777c0bf..0d985c62e9c1de709f7b14992446c7e271ecd137 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift @@ -1,5 +1,8 @@ +import AppCore import ComposableArchitecture import SwiftUI +import XXClient +import XXModels public struct ContactView: View { public init(store: Store<ContactState, ContactAction>) { @@ -9,13 +12,123 @@ public struct ContactView: View { let store: Store<ContactState, ContactAction> struct ViewState: Equatable { - init(state: ContactState) {} + var dbContact: XXModels.Contact? + var xxContact: XXClient.Contact? + + init(state: ContactState) { + dbContact = state.dbContact + xxContact = state.xxContact + } } public var body: some View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in Form { + Section { + if let dbContact = viewStore.dbContact { + Label(dbContact.username ?? "", systemImage: "person") + Label(dbContact.email ?? "", systemImage: "envelope") + Label(dbContact.phone ?? "", systemImage: "phone") + } else { + Text("Contact not saved locally") + } + } header: { + Text("Local data") + } + + Section { + Label(viewStore.xxContact?.username ?? "", systemImage: "person") + Label(viewStore.xxContact?.email ?? "", systemImage: "envelope") + Label(viewStore.xxContact?.phone ?? "", systemImage: "phone") + } header: { + Text("Facts") + } + + Section { + switch viewStore.dbContact?.authStatus { + case .none, .stranger: + HStack { + Text("Stranger") + Spacer() + Image(systemName: "person.fill.questionmark") + } + + case .requesting: + HStack { + Text("Sending auth request") + Spacer() + ProgressView() + } + + case .requested: + HStack { + Text("Request sent") + Spacer() + Image(systemName: "paperplane") + } + + case .requestFailed: + HStack { + Text("Sending request failed") + Spacer() + Image(systemName: "xmark.diamond.fill") + .foregroundColor(.red) + } + + case .verificationInProgress: + HStack { + Text("Verification is progress") + Spacer() + ProgressView() + } + + case .verified: + HStack { + Text("Verified") + Spacer() + Image(systemName: "person.fill.checkmark") + } + + case .verificationFailed: + HStack { + Text("Verification failed") + Spacer() + Image(systemName: "xmark.diamond.fill") + .foregroundColor(.red) + } + + case .confirming: + HStack { + Text("Confirming auth request") + Spacer() + ProgressView() + } + + case .confirmationFailed: + HStack { + Text("Confirmation failed") + Spacer() + Image(systemName: "xmark.diamond.fill") + .foregroundColor(.red) + } + + case .friend: + HStack { + Text("Friend") + Spacer() + Image(systemName: "person.fill.checkmark") + } + case .hidden: + HStack { + Text("Hidden") + Spacer() + Image(systemName: "eye.slash") + } + } + } header: { + Text("Auth status") + } } .navigationTitle("Contact") } diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift index 4711a13269fa4b8576b8280f7a3b90e717fa25a5..5145acce1eb9409a13968db64778746cbf6a3e53 100644 --- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift @@ -1,5 +1,8 @@ +import Combine import ComposableArchitecture +import CustomDump import XCTest +import XXModels @testable import ContactFeature final class ContactFeatureTests: XCTestCase { @@ -12,6 +15,33 @@ final class ContactFeatureTests: XCTestCase { environment: .unimplemented ) + var dbDidFetchContacts: [XXModels.Contact.Query] = [] + let dbContactsPublisher = PassthroughSubject<[XXModels.Contact], Error>() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.db.run = { + var db: Database = .failing + db.fetchContactsPublisher.run = { query in + dbDidFetchContacts.append(query) + return dbContactsPublisher.eraseToAnyPublisher() + } + return db + } + store.send(.start) + + XCTAssertNoDifference(dbDidFetchContacts, [ + .init(id: ["contact-id".data(using: .utf8)!]) + ]) + + let dbContact = XXModels.Contact(id: "contact-id".data(using: .utf8)!) + dbContactsPublisher.send([dbContact]) + + store.receive(.dbContactFetched(dbContact)) { + $0.dbContact = dbContact + } + + dbContactsPublisher.send(completion: .finished) } }