diff --git a/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupComponent.swift b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupComponent.swift index 9bac4d04a12ee801603e584fb92edb2b57bdab08..312fcefff0bdd6eec609db0dc81e180032368db2 100644 --- a/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupComponent.swift +++ b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupComponent.swift @@ -1,27 +1,33 @@ import AppCore import ComposableArchitecture import Foundation +import XXMessengerClient import XXModels public struct NewGroupComponent: ReducerProtocol { public struct State: Equatable { public init( - contacts: IdentifiedArrayOf<XXModels.Contact> = [] + contacts: IdentifiedArrayOf<XXModels.Contact> = [], + members: IdentifiedArrayOf<XXModels.Contact> = [] ) { self.contacts = contacts + self.members = members } public var contacts: IdentifiedArrayOf<XXModels.Contact> + public var members: IdentifiedArrayOf<XXModels.Contact> } public enum Action: Equatable { case start case didFetchContacts([XXModels.Contact]) + case didSelectContact(XXModels.Contact) case didFinish } public init() {} + @Dependency(\.app.messenger) var messenger: Messenger @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> @@ -29,10 +35,12 @@ public struct NewGroupComponent: ReducerProtocol { public func reduce(into state: inout State, action: Action) -> EffectTask<Action> { switch action { case .start: + let myId = try? messenger.e2e.tryGet().getContact().getId() return Effect .catching { try db() } .flatMap { $0.fetchContactsPublisher(.init()) } .assertNoFailure() + .map { $0.filter { $0.id != myId } } .map(Action.didFetchContacts) .subscribe(on: bgQueue) .receive(on: mainQueue) @@ -42,6 +50,14 @@ public struct NewGroupComponent: ReducerProtocol { state.contacts = IdentifiedArray(uniqueElements: contacts) return .none + case .didSelectContact(let contact): + if state.members.contains(contact) { + state.members.remove(contact) + } else { + state.members.append(contact) + } + return .none + case .didFinish: return .none } diff --git a/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupView.swift b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupView.swift index fa85a16fc9cfa01b4cee4c8d95bc1a5ce0aaea45..5540743b752653ca849731b8fbb00968a3b1b59b 100644 --- a/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupView.swift +++ b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupView.swift @@ -5,6 +5,7 @@ import XXModels public struct NewGroupView: View { public typealias Component = NewGroupComponent + typealias ViewStore = ComposableArchitecture.ViewStore<ViewState, Component.Action> public init(store: StoreOf<Component>) { self.store = store @@ -13,18 +14,46 @@ public struct NewGroupView: View { let store: StoreOf<Component> struct ViewState: Equatable { - init(state: Component.State) {} + init(state: Component.State) { + contacts = state.contacts + members = state.members + } + + var contacts: IdentifiedArrayOf<XXModels.Contact> + var members: IdentifiedArrayOf<XXModels.Contact> } public var body: some View { WithViewStore(store, observe: ViewState.init) { viewStore in Form { - + Section { + membersView(viewStore) + } } .navigationTitle("New Group") .task { viewStore.send(.start) } } } + + func membersView(_ viewStore: ViewStore) -> some View { + NavigationLink("Members (\(viewStore.members.count))") { + Form { + ForEach(viewStore.contacts) { contact in + Button { + viewStore.send(.didSelectContact(contact)) + } label: { + HStack { + Text(contact.username ?? "") + Spacer() + if viewStore.members.contains(contact) { + Image(systemName: "checkmark") + } + } + } + } + } + } + } } #if DEBUG diff --git a/Examples/xx-messenger/Tests/NewGroupFeatureTests/NewGroupComponentTests.swift b/Examples/xx-messenger/Tests/NewGroupFeatureTests/NewGroupComponentTests.swift index 04af1dca6d56f0f92c9f017399aacb81684052ff..45a71ad597f5a2b17b16cc3ade85fb03588f8913 100644 --- a/Examples/xx-messenger/Tests/NewGroupFeatureTests/NewGroupComponentTests.swift +++ b/Examples/xx-messenger/Tests/NewGroupFeatureTests/NewGroupComponentTests.swift @@ -2,6 +2,8 @@ import Combine import ComposableArchitecture import CustomDump import XCTest +import XXClient +import XXMessengerClient import XXModels @testable import NewGroupFeature @@ -30,6 +32,15 @@ final class NewGroupComponentTests: XCTestCase { store.dependencies.app.mainQueue = .immediate store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact = XXClient.Contact.unimplemented("my-contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "my-contact-id".data(using: .utf8)! } + return contact + } + return e2e + } store.dependencies.app.dbManager.getDB.run = { var db: Database = .unimplemented db.fetchContactsPublisher.run = { query in @@ -59,6 +70,33 @@ final class NewGroupComponentTests: XCTestCase { contactsSubject.send(completion: .finished) } + func testSelectMembers() { + let contacts: [XXModels.Contact] = [ + .init(id: "contact-1-id".data(using: .utf8)!), + .init(id: "contact-2-id".data(using: .utf8)!), + .init(id: "contact-3-id".data(using: .utf8)!), + ] + + let store = TestStore( + initialState: NewGroupComponent.State( + contacts: IdentifiedArray(uniqueElements: contacts) + ), + reducer: NewGroupComponent() + ) + + store.send(.didSelectContact(contacts[0])) { + $0.members = IdentifiedArray(uniqueElements: [contacts[0]]) + } + + store.send(.didSelectContact(contacts[1])) { + $0.members = IdentifiedArray(uniqueElements: [contacts[0], contacts[1]]) + } + + store.send(.didSelectContact(contacts[0])) { + $0.members = IdentifiedArray(uniqueElements: [contacts[1]]) + } + } + func testFinish() { let store = TestStore( initialState: NewGroupComponent.State(),