diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 1254c0e0f58a0028cce730198987a8e2d835039d..95a8720225901da1377b8adaf5625e88e65c02ac 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -69,6 +69,7 @@ let package = Package( name: "AppFeature", dependencies: [ .target(name: "AppCore"), + .target(name: "ContactFeature"), .target(name: "HomeFeature"), .target(name: "RegisterFeature"), .target(name: "RestoreFeature"), @@ -158,6 +159,7 @@ let package = Package( .target( name: "UserSearchFeature", dependencies: [ + .target(name: "ContactFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposablePresentation", package: "swift-composable-presentation"), .product(name: "XXClient", 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 d8761a65e8dd84a628cb8fb8c67231083c42cff2..03c8442cf0431924977536d9b3e84a6318087279 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -52,11 +52,10 @@ extension AppEnvironment { mainQueue: mainQueue, bgQueue: bgQueue, result: { - UserSearchResultEnvironment( - db: dbManager.getDB, - mainQueue: mainQueue, - bgQueue: bgQueue - ) + UserSearchResultEnvironment() + }, + contact: { + ContactEnvironment() } ) } diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift index 84c78195209c839f51b3d0771a41aad23a10659f..425832e26ee67b4a71bf3bd6086fd3269c26dbad 100644 --- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift +++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift @@ -1,5 +1,6 @@ import ComposableArchitecture import ComposablePresentation +import ContactFeature import Foundation import XCTestDynamicOverlay import XXClient @@ -17,13 +18,15 @@ public struct UserSearchState: Equatable { query: MessengerSearchUsers.Query = .init(), isSearching: Bool = false, failure: String? = nil, - results: IdentifiedArrayOf<UserSearchResultState> = [] + results: IdentifiedArrayOf<UserSearchResultState> = [], + contact: ContactState? = nil ) { self.focusedField = focusedField self.query = query self.isSearching = isSearching self.failure = failure self.results = results + self.contact = contact } @BindableState public var focusedField: Field? @@ -31,14 +34,17 @@ public struct UserSearchState: Equatable { public var isSearching: Bool public var failure: String? public var results: IdentifiedArrayOf<UserSearchResultState> + public var contact: ContactState? } public enum UserSearchAction: Equatable, BindableAction { case searchTapped case didFail(String) case didSucceed([Contact]) + case didDismissContact case binding(BindingAction<UserSearchState>) case result(id: UserSearchResultState.ID, action: UserSearchResultAction) + case contact(ContactAction) } public struct UserSearchEnvironment { @@ -46,18 +52,21 @@ public struct UserSearchEnvironment { messenger: Messenger, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue>, - result: @escaping () -> UserSearchResultEnvironment + result: @escaping () -> UserSearchResultEnvironment, + contact: @escaping () -> ContactEnvironment ) { self.messenger = messenger self.mainQueue = mainQueue self.bgQueue = bgQueue self.result = result + self.contact = contact } public var messenger: Messenger public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> public var result: () -> UserSearchResultEnvironment + public var contact: () -> ContactEnvironment } #if DEBUG @@ -66,7 +75,8 @@ extension UserSearchEnvironment { messenger: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented, - result: { .unimplemented } + result: { .unimplemented }, + contact: { .unimplemented } ) } #endif @@ -105,7 +115,15 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe state.results = [] return .none - case .binding(_), .result(_, _): + case .didDismissContact: + state.contact = nil + return .none + + case .result(let id, action: .tapped): + state.contact = ContactState() + return .none + + case .binding(_), .result(_, _), .contact(_): return .none } } @@ -116,3 +134,10 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe action: /UserSearchAction.result(id:action:), environment: { $0.result() } ) +.presenting( + contactReducer, + state: .keyPath(\.contact), + id: .notNil(), // TODO: use Contact.ID + action: /UserSearchAction.contact, + environment: { $0.contact() } +) diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift index e149ac2a9f4d7485a80331e6ac816e87f8b41aaf..f0416b3a53542ef4195a66ca077617327d62737c 100644 --- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift +++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift @@ -1,4 +1,6 @@ import ComposableArchitecture +import ComposablePresentation +import ContactFeature import SwiftUI import XXMessengerClient @@ -96,6 +98,14 @@ public struct UserSearchView: View { .onChange(of: viewStore.focusedField) { focusedField = $0 } .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) } .navigationTitle("User Search") + .background(NavigationLinkWithStore( + store.scope( + state: \.contact, + action: UserSearchAction.contact + ), + onDeactivate: { viewStore.send(.didDismissContact) }, + destination: ContactView.init(store:) + )) } } } diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift index 4311b515ccc209d9f84738bc5b57d7f1a8d7620e..285d359e7a8582ba52a10a8308f447f1f251b198 100644 --- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift +++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import ContactFeature import XCTest import XXClient import XXMessengerClient @@ -87,4 +88,37 @@ final class UserSearchFeatureTests: XCTestCase { $0.results = [] } } + + func testResultTapped() { + let store = TestStore( + initialState: UserSearchState( + results: [ + .init( + id: "contact-id".data(using: .utf8)!, + xxContact: .unimplemented("contact-data".data(using: .utf8)!) + ) + ] + ), + reducer: userSearchReducer, + environment: .unimplemented + ) + + store.send(.result(id: "contact-id".data(using: .utf8)!, action: .tapped)) { + $0.contact = ContactState() + } + } + + func testDismissingContact() { + let store = TestStore( + initialState: UserSearchState( + contact: ContactState() + ), + reducer: userSearchReducer, + environment: .unimplemented + ) + + store.send(.didDismissContact) { + $0.contact = nil + } + } } diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift index e776f803978b12c467701f371496d9bdd83d0a11..c8f2a99b92b35b645c1d3e94462e6353ec4d33fc 100644 --- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift +++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift @@ -1,9 +1,7 @@ -import Combine import ComposableArchitecture import XCTest import XCTestDynamicOverlay import XXClient -import XXModels @testable import UserSearchFeature final class UserSearchResultFeatureTests: XCTestCase { @@ -26,41 +24,14 @@ final class UserSearchResultFeatureTests: 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) { $0.username = "contact-username" $0.email = "contact-email" $0.phone = "contact-phone" } - - XCTAssertNoDifference(dbDidFetchContacts, [ - .init(id: ["contact-id".data(using: .utf8)!]) - ]) - - let dbContact = XXModels.Contact(id: "contact-id".data(using: .utf8)!) - dbContactsPublisher.send([dbContact]) - - store.receive(.didUpdateContact(dbContact)) { - $0.dbContact = dbContact - } - - dbContactsPublisher.send(completion: .finished) } - func testSendRequest() { + func testTapped() { let store = TestStore( initialState: UserSearchResultState( id: "contact-id".data(using: .utf8)!, @@ -70,6 +41,6 @@ final class UserSearchResultFeatureTests: XCTestCase { environment: .unimplemented ) - store.send(.sendRequestButtonTapped) + store.send(.tapped) } }