diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index 2caa4f0f5db019d0cc314441bb6b8c3cd64ef424..8ac8875116ffbe811331520ac53b88b6f5470a96 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -50,7 +50,11 @@ extension AppEnvironment { ) }, contacts: { - ContactsEnvironment() + ContactsEnvironment( + db: dbManager.getDB, + mainQueue: mainQueue, + bgQueue: bgQueue + ) }, userSearch: { UserSearchEnvironment( diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift index e7f98fe7ec33f6be6dbcdd755586ffd9d69eeeca..c578059407050024bdc0d2fd06859c91639189e5 100644 --- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift +++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift @@ -1,21 +1,47 @@ +import AppCore import ComposableArchitecture +import Foundation import XCTestDynamicOverlay +import XXModels public struct ContactsState: Equatable { - public init() {} + public init( + contacts: IdentifiedArrayOf<Contact> = [] + ) { + self.contacts = contacts + } + + public var contacts: IdentifiedArrayOf<XXModels.Contact> } public enum ContactsAction: Equatable { case start + case didFetchContacts([XXModels.Contact]) } public struct ContactsEnvironment { - public init() {} + public init( + db: DBManagerGetDB, + mainQueue: AnySchedulerOf<DispatchQueue>, + bgQueue: AnySchedulerOf<DispatchQueue> + ) { + self.db = db + self.mainQueue = mainQueue + self.bgQueue = bgQueue + } + + public var db: DBManagerGetDB + public var mainQueue: AnySchedulerOf<DispatchQueue> + public var bgQueue: AnySchedulerOf<DispatchQueue> } #if DEBUG extension ContactsEnvironment { - public static let unimplemented = ContactsEnvironment() + public static let unimplemented = ContactsEnvironment( + db: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) } #endif @@ -23,6 +49,17 @@ public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvi { state, action, env in switch action { case .start: + return Effect + .catching { try env.db() } + .flatMap { $0.fetchContactsPublisher(.init()) } + .assertNoFailure() + .map(ContactsAction.didFetchContacts) + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .didFetchContacts(let contacts): + state.contacts = IdentifiedArray(uniqueElements: contacts) return .none } } diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift index 870889f24dc92106ee79b8be4d96386583545e88..ad5d1a375b3315b83e4ae3510ccac2acfa6614ae 100644 --- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift +++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift @@ -1,5 +1,7 @@ +import AppCore import ComposableArchitecture import SwiftUI +import XXModels public struct ContactsView: View { public init(store: Store<ContactsState, ContactsAction>) { @@ -9,13 +11,36 @@ public struct ContactsView: View { let store: Store<ContactsState, ContactsAction> struct ViewState: Equatable { - init(state: ContactsState) {} + var contacts: IdentifiedArrayOf<XXModels.Contact> + + init(state: ContactsState) { + contacts = state.contacts + } } public var body: some View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in Form { - + ForEach(viewStore.contacts) { contact in + Section { + Button { + // TODO: + } label: { + HStack { + VStack(alignment: .leading, spacing: 8) { + Label(contact.username ?? "", systemImage: "person") + Label(contact.email ?? "", systemImage: "envelope") + Label(contact.phone ?? "", systemImage: "phone") + } + .font(.callout) + .tint(Color.primary) + Spacer() + Image(systemName: "chevron.forward") + } + } + ContactAuthStatusView(contact.authStatus) + } + } } .navigationTitle("Contacts") .task { viewStore.send(.start) } @@ -28,7 +53,25 @@ public struct ContactsView_Previews: PreviewProvider { public static var previews: some View { NavigationView { ContactsView(store: Store( - initialState: ContactsState(), + initialState: ContactsState( + contacts: [ + .init( + id: "1".data(using: .utf8)!, + username: "John Doe", + email: "john@doe.com", + phone: "+1234567890", + authStatus: .friend + ), + .init( + id: "2".data(using: .utf8)!, + username: "Alice Unknown", + authStatus: .requested + ), + .init( + id: "3".data(using: .utf8)! + ), + ] + ), reducer: .empty, environment: () )) diff --git a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift index 3b521768401dc21e68d690ec765ff46bd5a7cf5f..04533b34aaf32a062c01e7d93249fae0f2db662d 100644 --- a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift @@ -1,5 +1,8 @@ +import Combine import ComposableArchitecture +import CustomDump import XCTest +import XXModels @testable import ContactsFeature final class ContactsFeatureTests: XCTestCase { @@ -10,6 +13,35 @@ final class ContactsFeatureTests: XCTestCase { environment: .unimplemented ) + var didFetchContacts: [XXModels.Contact.Query] = [] + let contactsPublisher = 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 + didFetchContacts.append(query) + return contactsPublisher.eraseToAnyPublisher() + } + return db + } + store.send(.start) + + XCTAssertNoDifference(didFetchContacts, [XXModels.Contact.Query()]) + + let contacts: [XXModels.Contact] = [ + .init(id: "1".data(using: .utf8)!), + .init(id: "2".data(using: .utf8)!), + .init(id: "3".data(using: .utf8)!), + ] + contactsPublisher.send(contacts) + + store.receive(.didFetchContacts(contacts)) { + $0.contacts = IdentifiedArray(uniqueElements: contacts) + } + + contactsPublisher.send(completion: .finished) } }