Skip to content
Snippets Groups Projects
Commit d107bb58 authored by Dariusz Rybicki's avatar Dariusz Rybicki
Browse files

Remove UserSearchResultFeature

parent 39909b36
No related branches found
No related tags found
2 merge requests!102Release 1.0.0,!74Messenger example - clean up
......@@ -53,9 +53,6 @@ extension AppEnvironment {
messenger: messenger,
mainQueue: mainQueue,
bgQueue: bgQueue,
result: {
UserSearchResultEnvironment()
},
contact: {
ContactEnvironment(
messenger: messenger,
......
......@@ -13,12 +13,38 @@ public struct UserSearchState: Equatable {
case phone
}
public struct Result: Equatable, Identifiable {
public init(
id: Data,
xxContact: XXClient.Contact,
username: String? = nil,
email: String? = nil,
phone: String? = nil
) {
self.id = id
self.xxContact = xxContact
self.username = username
self.email = email
self.phone = phone
}
public var id: Data
public var xxContact: XXClient.Contact
public var username: String?
public var email: String?
public var phone: String?
public var hasFacts: Bool {
username != nil || email != nil || phone != nil
}
}
public init(
focusedField: Field? = nil,
query: MessengerSearchUsers.Query = .init(),
isSearching: Bool = false,
failure: String? = nil,
results: IdentifiedArrayOf<UserSearchResultState> = [],
results: IdentifiedArrayOf<Result> = [],
contact: ContactState? = nil
) {
self.focusedField = focusedField
......@@ -33,7 +59,7 @@ public struct UserSearchState: Equatable {
@BindableState public var query: MessengerSearchUsers.Query
public var isSearching: Bool
public var failure: String?
public var results: IdentifiedArrayOf<UserSearchResultState>
public var results: IdentifiedArrayOf<Result>
public var contact: ContactState?
}
......@@ -42,8 +68,8 @@ public enum UserSearchAction: Equatable, BindableAction {
case didFail(String)
case didSucceed([Contact])
case didDismissContact
case resultTapped(id: Data)
case binding(BindingAction<UserSearchState>)
case result(id: UserSearchResultState.ID, action: UserSearchResultAction)
case contact(ContactAction)
}
......@@ -52,20 +78,17 @@ public struct UserSearchEnvironment {
messenger: Messenger,
mainQueue: AnySchedulerOf<DispatchQueue>,
bgQueue: AnySchedulerOf<DispatchQueue>,
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
}
......@@ -75,7 +98,6 @@ extension UserSearchEnvironment {
messenger: .unimplemented,
mainQueue: .unimplemented,
bgQueue: .unimplemented,
result: { .unimplemented },
contact: { .unimplemented }
)
}
......@@ -105,7 +127,13 @@ 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 }
return UserSearchResultState(id: id, xxContact: contact)
return UserSearchState.Result(
id: id,
xxContact: contact,
username: try? contact.getFact(.username)?.fact,
email: try? contact.getFact(.email)?.fact,
phone: try? contact.getFact(.phone)?.fact
)
})
return .none
......@@ -119,24 +147,18 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe
state.contact = nil
return .none
case .result(let id, action: .tapped):
case .resultTapped(let id):
state.contact = ContactState(
id: id,
xxContact: state.results[id: id]?.xxContact
)
return .none
case .binding(_), .result(_, _), .contact(_):
case .binding(_), .contact(_):
return .none
}
}
.binding()
.presenting(
forEach: userSearchResultReducer,
state: \.results,
action: /UserSearchAction.result(id:action:),
environment: { $0.result() }
)
.presenting(
contactReducer,
state: .keyPath(\.contact),
......
import ComposableArchitecture
import Foundation
import XCTestDynamicOverlay
import XXClient
public struct UserSearchResultState: Equatable, Identifiable {
public init(
id: Data,
xxContact: XXClient.Contact,
username: String? = nil,
email: String? = nil,
phone: String? = nil
) {
self.id = id
self.xxContact = xxContact
self.username = username
self.email = email
self.phone = phone
}
public var id: Data
public var xxContact: XXClient.Contact
public var username: String?
public var email: String?
public var phone: String?
}
public enum UserSearchResultAction: Equatable {
case start
case tapped
}
public struct UserSearchResultEnvironment {
public init() {}
}
#if DEBUG
extension UserSearchResultEnvironment {
public static let unimplemented = UserSearchResultEnvironment()
}
#endif
public let userSearchResultReducer = Reducer<UserSearchResultState, UserSearchResultAction, UserSearchResultEnvironment>
{ state, action, env in
switch action {
case .start:
state.username = try? state.xxContact.getFact(.username)?.fact
state.email = try? state.xxContact.getFact(.email)?.fact
state.phone = try? state.xxContact.getFact(.phone)?.fact
return .none
case .tapped:
return .none
}
}
import ComposableArchitecture
import SwiftUI
import XXModels
public struct UserSearchResultView: View {
public init(store: Store<UserSearchResultState, UserSearchResultAction>) {
self.store = store
}
let store: Store<UserSearchResultState, UserSearchResultAction>
struct ViewState: Equatable {
var username: String?
var email: String?
var phone: String?
init(state: UserSearchResultState) {
username = state.username
email = state.email
phone = state.phone
}
var isEmpty: Bool {
username == nil && email == nil && phone == nil
}
}
public var body: some View {
WithViewStore(store.scope(state: ViewState.init)) { viewStore in
Section {
Button {
viewStore.send(.tapped)
} label: {
HStack {
VStack {
if viewStore.isEmpty {
Image(systemName: "questionmark")
.frame(maxWidth: .infinity)
} else {
if let username = viewStore.username {
Text(username)
}
if let email = viewStore.email {
Text(email)
}
if let phone = viewStore.phone {
Text(phone)
}
}
}
Spacer()
Image(systemName: "chevron.forward")
}
}
}
.task { viewStore.send(.start) }
}
}
}
#if DEBUG
public struct UserSearchResultView_Previews: PreviewProvider {
public static var previews: some View {
UserSearchResultView(store: Store(
initialState: UserSearchResultState(
id: "contact-id".data(using: .utf8)!,
xxContact: .unimplemented("contact-data".data(using: .utf8)!)
),
reducer: .empty,
environment: ()
))
}
}
#endif
......@@ -17,12 +17,14 @@ 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,13 +89,34 @@ public struct UserSearchView: View {
}
}
ForEachStore(
store.scope(
state: \.results,
action: UserSearchAction.result(id:action:)
),
content: UserSearchResultView.init(store:)
)
ForEach(viewStore.results) { result in
Section {
Button {
viewStore.send(.resultTapped(id: result.id))
} label: {
HStack {
VStack {
if result.hasFacts {
if let username = result.username {
Text(username)
}
if let email = result.email {
Text(email)
}
if let phone = result.phone {
Text(phone)
}
} else {
Image(systemName: "questionmark")
.frame(maxWidth: .infinity)
}
}
Spacer()
Image(systemName: "chevron.forward")
}
}
}
}
}
.onChange(of: viewStore.focusedField) { focusedField = $0 }
.onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
......
......@@ -20,12 +20,26 @@ final class UserSearchFeatureTests: XCTestCase {
var contact1 = Contact.unimplemented("contact-1".data(using: .utf8)!)
contact1.getIdFromContact.run = { _ in "contact-1-id".data(using: .utf8)! }
var contact2 = Contact.unimplemented("contact-1".data(using: .utf8)!)
contact1.getFactsFromContact.run = { _ in
[
Fact(type: .username, value: "contact-1-username"),
Fact(type: .email, value: "contact-1-email"),
Fact(type: .phone, value: "contact-1-phone"),
]
}
var contact2 = Contact.unimplemented("contact-2".data(using: .utf8)!)
contact2.getIdFromContact.run = { _ in "contact-2-id".data(using: .utf8)! }
contact2.getFactsFromContact.run = { _ in
[
Fact(type: .username, value: "contact-2-username"),
]
}
var contact3 = Contact.unimplemented("contact-3".data(using: .utf8)!)
contact3.getIdFromContact.run = { _ in throw GetIdFromContactError() }
contact3.getFactsFromContact.run = { _ in [] }
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
......@@ -54,9 +68,22 @@ final class UserSearchFeatureTests: XCTestCase {
$0.isSearching = false
$0.failure = nil
$0.results = [
.init(id: "contact-1-id".data(using: .utf8)!, xxContact: contact1),
.init(id: "contact-2-id".data(using: .utf8)!, xxContact: contact2),
.init(id: "contact-4-id".data(using: .utf8)!, xxContact: contact4)
.init(
id: "contact-1-id".data(using: .utf8)!,
xxContact: contact1,
username: "contact-1-username",
email: "contact-1-email",
phone: "contact-1-phone"
),
.init(
id: "contact-2-id".data(using: .utf8)!,
xxContact: contact2,
username: "contact-2-username"
),
.init(
id: "contact-4-id".data(using: .utf8)!,
xxContact: contact4
)
]
}
}
......@@ -103,7 +130,7 @@ final class UserSearchFeatureTests: XCTestCase {
environment: .unimplemented
)
store.send(.result(id: "contact-id".data(using: .utf8)!, action: .tapped)) {
store.send(.resultTapped(id: "contact-id".data(using: .utf8)!)) {
$0.contact = ContactState(
id: "contact-id".data(using: .utf8)!,
xxContact: .unimplemented("contact-data".data(using: .utf8)!)
......
import ComposableArchitecture
import XCTest
import XCTestDynamicOverlay
import XXClient
@testable import UserSearchFeature
final class UserSearchResultFeatureTests: XCTestCase {
func testStart() {
var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
contact.getFactsFromContact.run = { _ in
[
Fact(fact: "contact-username", type: 0),
Fact(fact: "contact-email", type: 1),
Fact(fact: "contact-phone", type: 2),
]
}
let store = TestStore(
initialState: UserSearchResultState(
id: "contact-id".data(using: .utf8)!,
xxContact: contact
),
reducer: userSearchResultReducer,
environment: .unimplemented
)
store.send(.start) {
$0.username = "contact-username"
$0.email = "contact-email"
$0.phone = "contact-phone"
}
}
func testTapped() {
let store = TestStore(
initialState: UserSearchResultState(
id: "contact-id".data(using: .utf8)!,
xxContact: .unimplemented("contact-data".data(using: .utf8)!)
),
reducer: userSearchResultReducer,
environment: .unimplemented
)
store.send(.tapped)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment