diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index f9ceb75fdf29afbda5362c20139003d77ebb131f..565dccc645fa11eabea768dccf2ee4e1f9df9475 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -141,6 +141,7 @@ let package = Package( name: "UserSearchFeature", dependencies: [ .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "ComposablePresentation", package: "swift-composable-presentation"), .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), ], diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index 5d7777802ed09dcfb2e36b14ba2a5b55f1fe906e..00415b4382a796f43ab1749e9ec3e4b6139c2562 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -50,7 +50,10 @@ extension AppEnvironment { UserSearchEnvironment( messenger: messenger, mainQueue: mainQueue, - bgQueue: bgQueue + bgQueue: bgQueue, + result: { + UserSearchResultEnvironment() + } ) } ) diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift index 86a13c0ec97966619bea881f5ac0b8c9de962fed..9d16d8f5fb18510ca0d794dfd4fd3933cf5faa4e 100644 --- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift +++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import ComposablePresentation import Foundation import XCTestDynamicOverlay import XXClient @@ -11,34 +12,12 @@ public struct UserSearchState: Equatable { case phone } - public struct Result: Equatable, Identifiable { - public init( - id: Data, - contact: Contact, - username: String? = nil, - email: String? = nil, - phone: String? = nil - ) { - self.id = id - self.contact = contact - self.username = username - self.email = email - self.phone = phone - } - - public var id: Data - public var contact: XXClient.Contact - public var username: String? - public var email: String? - public var phone: String? - } - public init( focusedField: Field? = nil, query: MessengerSearchUsers.Query = .init(), isSearching: Bool = false, failure: String? = nil, - results: IdentifiedArrayOf<Result> = [] + results: IdentifiedArrayOf<UserSearchResultState> = [] ) { self.focusedField = focusedField self.query = query @@ -51,7 +30,7 @@ public struct UserSearchState: Equatable { @BindableState public var query: MessengerSearchUsers.Query public var isSearching: Bool public var failure: String? - public var results: IdentifiedArrayOf<Result> + public var results: IdentifiedArrayOf<UserSearchResultState> } public enum UserSearchAction: Equatable, BindableAction { @@ -59,22 +38,26 @@ public enum UserSearchAction: Equatable, BindableAction { case didFail(String) case didSucceed([Contact]) case binding(BindingAction<UserSearchState>) + case result(id: UserSearchResultState.ID, action: UserSearchResultAction) } public struct UserSearchEnvironment { public init( messenger: Messenger, mainQueue: AnySchedulerOf<DispatchQueue>, - bgQueue: AnySchedulerOf<DispatchQueue> + bgQueue: AnySchedulerOf<DispatchQueue>, + result: @escaping () -> UserSearchResultEnvironment ) { self.messenger = messenger self.mainQueue = mainQueue self.bgQueue = bgQueue + self.result = result } public var messenger: Messenger public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> + public var result: () -> UserSearchResultEnvironment } #if DEBUG @@ -82,7 +65,8 @@ extension UserSearchEnvironment { public static let unimplemented = UserSearchEnvironment( messenger: .unimplemented, mainQueue: .unimplemented, - bgQueue: .unimplemented + bgQueue: .unimplemented, + result: { .unimplemented } ) } #endif @@ -111,14 +95,7 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe state.failure = nil state.results = IdentifiedArray(uniqueElements: contacts.compactMap { contact in guard let id = try? contact.getId() else { return nil } - let facts = (try? contact.getFacts()) ?? [] - return UserSearchState.Result( - id: id, - contact: contact, - username: facts.first(where: { $0.type == 0 })?.fact, - email: facts.first(where: { $0.type == 1 })?.fact, - phone: facts.first(where: { $0.type == 2 })?.fact - ) + return UserSearchResultState(id: id, contact: contact) }) return .none @@ -128,8 +105,14 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe state.results = [] return .none - case .binding(_): + case .binding(_), .result(_, _): return .none } } .binding() +.presenting( + forEach: userSearchResultReducer, + state: \.results, + action: /UserSearchAction.result(id:action:), + environment: { $0.result() } +) diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift index 266938a29b096d05f695c2f39f55f8c0fb993442..e149ac2a9f4d7485a80331e6ac816e87f8b41aaf 100644 --- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift +++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift @@ -15,14 +15,12 @@ public struct UserSearchView: View { var query: MessengerSearchUsers.Query var isSearching: Bool var failure: String? - var results: IdentifiedArrayOf<UserSearchState.Result> init(state: UserSearchState) { focusedField = state.focusedField query = state.query isSearching = state.isSearching failure = state.failure - results = state.results } } @@ -87,23 +85,13 @@ public struct UserSearchView: View { } } - ForEach(viewStore.results) { result in - Section { - if let username = result.username { - Text(username) - } - if let email = result.email { - Text(email) - } - if let phone = result.phone { - Text(phone) - } - if result.username == nil, result.email == nil, result.phone == nil { - Image(systemName: "questionmark") - .frame(maxWidth: .infinity) - } - } - } + ForEachStore( + store.scope( + state: \.results, + action: UserSearchAction.result(id:action:) + ), + content: UserSearchResultView.init(store:) + ) } .onChange(of: viewStore.focusedField) { focusedField = $0 } .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) } diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift index 8d2f414a0a6c4fa130ea285863ae92ef80f70c21..a96afd9590cfdd4532f2c93716c6f5237a9c2096 100644 --- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift +++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift @@ -19,23 +19,12 @@ final class UserSearchFeatureTests: XCTestCase { var contact1 = Contact.unimplemented("contact-1".data(using: .utf8)!) contact1.getIdFromContact.run = { _ in "contact-1-id".data(using: .utf8)! } - contact1.getFactsFromContact.run = { _ in - [Fact(fact: "contact-1-username", type: 0), - Fact(fact: "contact-1-email", type: 1), - Fact(fact: "contact-1-phone", type: 2)] - } var contact2 = Contact.unimplemented("contact-1".data(using: .utf8)!) contact2.getIdFromContact.run = { _ in "contact-2-id".data(using: .utf8)! } - contact2.getFactsFromContact.run = { _ in - [Fact(fact: "contact-2-username", type: 0), - Fact(fact: "contact-2-email", type: 1), - Fact(fact: "contact-2-phone", type: 2)] - } var contact3 = Contact.unimplemented("contact-3".data(using: .utf8)!) contact3.getIdFromContact.run = { _ in throw GetIdFromContactError() } var contact4 = Contact.unimplemented("contact-4".data(using: .utf8)!) contact4.getIdFromContact.run = { _ in "contact-4-id".data(using: .utf8)! } - contact4.getFactsFromContact.run = { _ in throw GetFactsFromContactError() } let contacts = [contact1, contact2, contact3, contact4] store.environment.bgQueue = .immediate @@ -64,27 +53,9 @@ final class UserSearchFeatureTests: XCTestCase { $0.isSearching = false $0.failure = nil $0.results = [ - .init( - id: "contact-1-id".data(using: .utf8)!, - contact: contact1, - username: "contact-1-username", - email: "contact-1-email", - phone: "contact-1-phone" - ), - .init( - id: "contact-2-id".data(using: .utf8)!, - contact: contact2, - username: "contact-2-username", - email: "contact-2-email", - phone: "contact-2-phone" - ), - .init( - id: "contact-4-id".data(using: .utf8)!, - contact: contact4, - username: nil, - email: nil, - phone: nil - ) + .init(id: "contact-1-id".data(using: .utf8)!, contact: contact1), + .init(id: "contact-2-id".data(using: .utf8)!, contact: contact2), + .init(id: "contact-4-id".data(using: .utf8)!, contact: contact4) ] } }