mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 04:18:31 +00:00
* Fixes #2518, fixes #2590 - Implement public room search list pagination and room joining * Address PR comments
This commit is contained in:
@@ -577,14 +577,19 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// MARK: Room Directory Search
|
// MARK: Room Directory Search
|
||||||
|
|
||||||
private func presentRoomDirectorySearch() {
|
private func presentRoomDirectorySearch() {
|
||||||
let coordinator = RoomDirectorySearchScreenCoordinator(parameters: .init(roomDirectorySearchProxy: userSession.clientProxy.roomDirectorySearchProxy(),
|
let coordinator = RoomDirectorySearchScreenCoordinator(parameters: .init(clientProxy: userSession.clientProxy,
|
||||||
imageProvider: userSession.mediaProvider,
|
imageProvider: userSession.mediaProvider,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController))
|
userIndicatorController: ServiceLocator.shared.userIndicatorController))
|
||||||
|
|
||||||
coordinator.actionsPublisher.sink { [weak self] action in
|
coordinator.actionsPublisher.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
|
case .joined(let roomID):
|
||||||
|
stateMachine.processEvent(.dismissedRoomDirectorySearchScreen)
|
||||||
|
handleAppRoute(.room(roomID: roomID), animated: true)
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
self?.stateMachine.processEvent(.dismissedRoomDirectorySearchScreen(joinedRoomID: nil))
|
stateMachine.processEvent(.dismissedRoomDirectorySearchScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
case dismissedLogoutConfirmationScreen
|
case dismissedLogoutConfirmationScreen
|
||||||
|
|
||||||
case showRoomDirectorySearchScreen
|
case showRoomDirectorySearchScreen
|
||||||
case dismissedRoomDirectorySearchScreen(joinedRoomID: String?)
|
|
||||||
|
case dismissedRoomDirectorySearchScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
private let stateMachine: StateMachine<State, Event>
|
private let stateMachine: StateMachine<State, Event>
|
||||||
@@ -144,10 +145,7 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
|
|
||||||
case (.roomList(let selectedRoomID), .showRoomDirectorySearchScreen):
|
case (.roomList(let selectedRoomID), .showRoomDirectorySearchScreen):
|
||||||
return .roomDirectorySearchScreen(selectedRoomID: selectedRoomID)
|
return .roomDirectorySearchScreen(selectedRoomID: selectedRoomID)
|
||||||
case (.roomDirectorySearchScreen(let selectedRoomID), .dismissedRoomDirectorySearchScreen(let joinedRoomID)):
|
case (.roomDirectorySearchScreen(let selectedRoomID), .dismissedRoomDirectorySearchScreen):
|
||||||
if let joinedRoomID {
|
|
||||||
return .roomList(selectedRoomID: joinedRoomID)
|
|
||||||
}
|
|
||||||
return .roomList(selectedRoomID: selectedRoomID)
|
return .roomList(selectedRoomID: selectedRoomID)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ struct ClientProxyMockConfiguration {
|
|||||||
var userID: String = RoomMemberProxyMock.mockMe.userID
|
var userID: String = RoomMemberProxyMock.mockMe.userID
|
||||||
var deviceID: String?
|
var deviceID: String?
|
||||||
var roomSummaryProvider: RoomSummaryProviderProtocol? = RoomSummaryProviderMock(.init())
|
var roomSummaryProvider: RoomSummaryProviderProtocol? = RoomSummaryProviderMock(.init())
|
||||||
|
var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol?
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ClientProxyMock {
|
extension ClientProxyMock {
|
||||||
@@ -36,6 +37,8 @@ extension ClientProxyMock {
|
|||||||
alternateRoomSummaryProvider = RoomSummaryProviderMock(.init())
|
alternateRoomSummaryProvider = RoomSummaryProviderMock(.init())
|
||||||
inviteSummaryProvider = RoomSummaryProviderMock(.init())
|
inviteSummaryProvider = RoomSummaryProviderMock(.init())
|
||||||
|
|
||||||
|
roomDirectorySearchProxyReturnValue = configuration.roomDirectorySearchProxy
|
||||||
|
|
||||||
actionsPublisher = PassthroughSubject<ClientProxyAction, Never>().eraseToAnyPublisher()
|
actionsPublisher = PassthroughSubject<ClientProxyAction, Never>().eraseToAnyPublisher()
|
||||||
loadingStatePublisher = CurrentValuePublisher<ClientProxyLoadingState, Never>(.notLoading)
|
loadingStatePublisher = CurrentValuePublisher<ClientProxyLoadingState, Never>(.notLoading)
|
||||||
verificationStatePublisher = CurrentValuePublisher<SessionVerificationState, Never>(.unknown)
|
verificationStatePublisher = CurrentValuePublisher<SessionVerificationState, Never>(.unknown)
|
||||||
|
|||||||
@@ -898,6 +898,27 @@ class ClientProxyMock: ClientProxyProtocol {
|
|||||||
return createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue
|
return createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - joinRoom
|
||||||
|
|
||||||
|
var joinRoomCallsCount = 0
|
||||||
|
var joinRoomCalled: Bool {
|
||||||
|
return joinRoomCallsCount > 0
|
||||||
|
}
|
||||||
|
var joinRoomReceivedRoomID: String?
|
||||||
|
var joinRoomReceivedInvocations: [String] = []
|
||||||
|
var joinRoomReturnValue: Result<Void, ClientProxyError>!
|
||||||
|
var joinRoomClosure: ((String) async -> Result<Void, ClientProxyError>)?
|
||||||
|
|
||||||
|
func joinRoom(_ roomID: String) async -> Result<Void, ClientProxyError> {
|
||||||
|
joinRoomCallsCount += 1
|
||||||
|
joinRoomReceivedRoomID = roomID
|
||||||
|
joinRoomReceivedInvocations.append(roomID)
|
||||||
|
if let joinRoomClosure = joinRoomClosure {
|
||||||
|
return await joinRoomClosure(roomID)
|
||||||
|
} else {
|
||||||
|
return joinRoomReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - uploadMedia
|
//MARK: - uploadMedia
|
||||||
|
|
||||||
var uploadMediaCallsCount = 0
|
var uploadMediaCallsCount = 0
|
||||||
|
|||||||
+8
-3
@@ -18,12 +18,13 @@ import Combine
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RoomDirectorySearchScreenCoordinatorParameters {
|
struct RoomDirectorySearchScreenCoordinatorParameters {
|
||||||
let roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol
|
let clientProxy: ClientProxyProtocol
|
||||||
let imageProvider: ImageProviderProtocol
|
let imageProvider: ImageProviderProtocol
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomDirectorySearchScreenCoordinatorAction {
|
enum RoomDirectorySearchScreenCoordinatorAction {
|
||||||
|
case joined(roomID: String)
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,15 +39,19 @@ final class RoomDirectorySearchScreenCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(parameters: RoomDirectorySearchScreenCoordinatorParameters) {
|
init(parameters: RoomDirectorySearchScreenCoordinatorParameters) {
|
||||||
viewModel = RoomDirectorySearchScreenViewModel(roomDirectorySearch: parameters.roomDirectorySearchProxy, userIndicatorController: parameters.userIndicatorController, imageProvider: parameters.imageProvider)
|
viewModel = RoomDirectorySearchScreenViewModel(clientProxy: parameters.clientProxy,
|
||||||
|
userIndicatorController: parameters.userIndicatorController,
|
||||||
|
imageProvider: parameters.imageProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
viewModel.actionsPublisher.sink { [weak self] action in
|
viewModel.actionsPublisher.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
|
case .joined(let roomID):
|
||||||
|
actionsSubject.send(.joined(roomID: roomID))
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
self.actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
+3
-1
@@ -17,11 +17,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum RoomDirectorySearchScreenViewModelAction {
|
enum RoomDirectorySearchScreenViewModelAction {
|
||||||
|
case joined(roomID: String)
|
||||||
case dismiss
|
case dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomDirectorySearchScreenViewState: BindableState {
|
struct RoomDirectorySearchScreenViewState: BindableState {
|
||||||
var searchResults: [RoomDirectorySearchResult] = []
|
var rooms: [RoomDirectorySearchResult] = []
|
||||||
var isLoading = false
|
var isLoading = false
|
||||||
|
|
||||||
var bindings = RoomDirectorySearchScreenViewStateBindings()
|
var bindings = RoomDirectorySearchScreenViewStateBindings()
|
||||||
@@ -35,4 +36,5 @@ struct RoomDirectorySearchScreenViewStateBindings {
|
|||||||
enum RoomDirectorySearchScreenViewAction {
|
enum RoomDirectorySearchScreenViewAction {
|
||||||
case dismiss
|
case dismiss
|
||||||
case join(roomID: String)
|
case join(roomID: String)
|
||||||
|
case reachedBottom
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-7
@@ -20,7 +20,8 @@ import SwiftUI
|
|||||||
typealias RoomDirectorySearchScreenViewModelType = StateStoreViewModel<RoomDirectorySearchScreenViewState, RoomDirectorySearchScreenViewAction>
|
typealias RoomDirectorySearchScreenViewModelType = StateStoreViewModel<RoomDirectorySearchScreenViewState, RoomDirectorySearchScreenViewAction>
|
||||||
|
|
||||||
class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType, RoomDirectorySearchScreenViewModelProtocol {
|
class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType, RoomDirectorySearchScreenViewModelProtocol {
|
||||||
private let roomDirectorySearch: RoomDirectorySearchProxyProtocol
|
private let clientProxy: ClientProxyProtocol
|
||||||
|
private let roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
|
||||||
private let actionsSubject: PassthroughSubject<RoomDirectorySearchScreenViewModelAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<RoomDirectorySearchScreenViewModelAction, Never> = .init()
|
||||||
@@ -28,16 +29,20 @@ class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(roomDirectorySearch: RoomDirectorySearchProxyProtocol,
|
init(clientProxy: ClientProxyProtocol,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol,
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
imageProvider: ImageProviderProtocol) {
|
imageProvider: ImageProviderProtocol) {
|
||||||
self.roomDirectorySearch = roomDirectorySearch
|
self.clientProxy = clientProxy
|
||||||
|
roomDirectorySearchProxy = clientProxy.roomDirectorySearchProxy()
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
|
|
||||||
super.init(initialViewState: RoomDirectorySearchScreenViewState(), imageProvider: imageProvider)
|
super.init(initialViewState: RoomDirectorySearchScreenViewState(), imageProvider: imageProvider)
|
||||||
|
|
||||||
roomDirectorySearch.resultsPublisher
|
state.rooms = roomDirectorySearchProxy.resultsPublisher.value
|
||||||
|
|
||||||
|
roomDirectorySearchProxy.resultsPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.weakAssign(to: \.state.searchResults, on: self)
|
.weakAssign(to: \.state.rooms, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
context.$viewState.map(\.bindings.searchString)
|
context.$viewState.map(\.bindings.searchString)
|
||||||
@@ -62,10 +67,30 @@ class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType
|
|||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
case .join(roomID: let roomID):
|
case .join(roomID: let roomID):
|
||||||
joinRoom(roomID: roomID)
|
joinRoom(roomID: roomID)
|
||||||
|
case .reachedBottom:
|
||||||
|
loadNextPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func joinRoom(roomID: String) { }
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func joinRoom(roomID: String) {
|
||||||
|
showLoadingIndicator()
|
||||||
|
|
||||||
|
Task {
|
||||||
|
defer {
|
||||||
|
hideLoadingIndicator()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await clientProxy.joinRoom(roomID) {
|
||||||
|
case .success:
|
||||||
|
actionsSubject.send(.joined(roomID: roomID))
|
||||||
|
case .failure(let error):
|
||||||
|
MXLog.error("Failed joining room with error: \(error)")
|
||||||
|
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static let errorID = "roomDirectorySearchViewModelLoadingError"
|
private static let errorID = "roomDirectorySearchViewModelLoadingError"
|
||||||
|
|
||||||
@@ -74,9 +99,10 @@ class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.rooms = []
|
||||||
state.isLoading = true
|
state.isLoading = true
|
||||||
Task {
|
Task {
|
||||||
switch await roomDirectorySearch.search(query: query) {
|
switch await roomDirectorySearchProxy.search(query: query) {
|
||||||
case .success:
|
case .success:
|
||||||
break
|
break
|
||||||
case .failure:
|
case .failure:
|
||||||
@@ -88,4 +114,25 @@ class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType
|
|||||||
state.isLoading = false
|
state.isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func loadNextPage() {
|
||||||
|
Task {
|
||||||
|
state.isLoading = true
|
||||||
|
let _ = await roomDirectorySearchProxy.nextPage()
|
||||||
|
state.isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let loadingIndicatorIdentifier = "\(RoomDirectorySearchScreenViewModel.self)-Loading"
|
||||||
|
|
||||||
|
private func showLoadingIndicator() {
|
||||||
|
userIndicatorController.submitIndicator(.init(id: Self.loadingIndicatorIdentifier,
|
||||||
|
type: .modal,
|
||||||
|
title: L10n.commonLoading,
|
||||||
|
persistent: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hideLoadingIndicator() {
|
||||||
|
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import SwiftUI
|
|||||||
struct RoomDirectorySearchCell: View {
|
struct RoomDirectorySearchCell: View {
|
||||||
let result: RoomDirectorySearchResult
|
let result: RoomDirectorySearchResult
|
||||||
let imageProvider: ImageProviderProtocol?
|
let imageProvider: ImageProviderProtocol?
|
||||||
|
let joinAction: () -> Void
|
||||||
|
|
||||||
private var description: String? {
|
private var description: String? {
|
||||||
if let topic = result.topic {
|
if let topic = result.topic {
|
||||||
@@ -36,9 +37,17 @@ struct RoomDirectorySearchCell: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ListRow(label: .avatar(title: result.name ?? result.alias ?? result.id,
|
if result.canBeJoined {
|
||||||
description: description,
|
ListRow(label: .avatar(title: result.name ?? result.alias ?? result.id,
|
||||||
icon: avatar), kind: .label)
|
description: description,
|
||||||
|
icon: avatar),
|
||||||
|
details: .label(title: L10n.actionJoin, icon: EmptyView()),
|
||||||
|
kind: .navigationLink(action: joinAction))
|
||||||
|
} else {
|
||||||
|
ListRow(label: .avatar(title: result.name ?? result.alias ?? result.id,
|
||||||
|
description: description,
|
||||||
|
icon: avatar), kind: .label)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var avatar: some View {
|
private var avatar: some View {
|
||||||
@@ -56,14 +65,69 @@ struct RoomDirectorySearchCell: View {
|
|||||||
struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview {
|
struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
List {
|
List {
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_1:matrix.org", alias: "#test:example.com", name: "Test title", topic: "test description", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
RoomDirectorySearchCell(result: .init(id: "!test_id_1:matrix.org",
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_2:matrix.org", alias: "#test:example.com", name: nil, topic: "test description", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
alias: "#test:example.com",
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_3:example.com", alias: "#test_no_topic:example.com", name: "Test title no topic", topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
name: "Test title",
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_4:example.com", alias: "#test_no_topic:example.com", name: nil, topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
topic: "test description",
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_5:example.com", alias: nil, name: "Test title no alias", topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
avatarURL: nil,
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_6:example.com", alias: nil, name: "Test title no alias", topic: "Topic", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
canBeJoined: false),
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_7:example.com", alias: nil, name: nil, topic: "Topic", avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
imageProvider: MockMediaProvider()) { }
|
||||||
RoomDirectorySearchCell(result: .init(id: "!test_id_8:example.com", alias: nil, name: nil, topic: nil, avatarURL: nil, canBeJoined: false), imageProvider: MockMediaProvider())
|
|
||||||
|
RoomDirectorySearchCell(result: .init(id: "!test_id_2:matrix.org",
|
||||||
|
alias: "#test:example.com",
|
||||||
|
name: nil,
|
||||||
|
topic: "test description",
|
||||||
|
avatarURL: nil,
|
||||||
|
canBeJoined: false),
|
||||||
|
imageProvider: MockMediaProvider()) { }
|
||||||
|
|
||||||
|
RoomDirectorySearchCell(result: .init(id: "!test_id_3:example.com",
|
||||||
|
alias: "#test_no_topic:example.com",
|
||||||
|
name: "Test title no topic",
|
||||||
|
topic: nil,
|
||||||
|
avatarURL: nil,
|
||||||
|
canBeJoined: false),
|
||||||
|
imageProvider: MockMediaProvider()) { }
|
||||||
|
|
||||||
|
RoomDirectorySearchCell(result: .init(id: "!test_id_4:example.com",
|
||||||
|
alias: "#test_no_topic:example.com",
|
||||||
|
name: nil,
|
||||||
|
topic: nil,
|
||||||
|
avatarURL: nil,
|
||||||
|
canBeJoined: false),
|
||||||
|
imageProvider: MockMediaProvider()) { }
|
||||||
|
|
||||||
|
RoomDirectorySearchCell(result: .init(id: "!test_id_5:example.com",
|
||||||
|
alias: nil,
|
||||||
|
name: "Test title no alias",
|
||||||
|
topic: nil,
|
||||||
|
avatarURL: nil,
|
||||||
|
canBeJoined: false),
|
||||||
|
imageProvider: MockMediaProvider()) { }
|
||||||
|
|
||||||
|
RoomDirectorySearchCell(result: .init(id: "!test_id_6:example.com",
|
||||||
|
alias: nil,
|
||||||
|
name: "Test title no alias",
|
||||||
|
topic: "Topic",
|
||||||
|
avatarURL: nil,
|
||||||
|
canBeJoined: false),
|
||||||
|
imageProvider: MockMediaProvider()) { }
|
||||||
|
|
||||||
|
RoomDirectorySearchCell(result: .init(id: "!test_id_7:example.com",
|
||||||
|
alias: nil,
|
||||||
|
name: nil,
|
||||||
|
topic: "Topic",
|
||||||
|
avatarURL: nil,
|
||||||
|
canBeJoined: false),
|
||||||
|
imageProvider: MockMediaProvider()) { }
|
||||||
|
RoomDirectorySearchCell(result: .init(id: "!test_id_8:example.com",
|
||||||
|
|
||||||
|
alias: nil,
|
||||||
|
name: nil,
|
||||||
|
topic: nil,
|
||||||
|
avatarURL: nil,
|
||||||
|
canBeJoined: false),
|
||||||
|
imageProvider: MockMediaProvider()) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-16
@@ -24,8 +24,22 @@ struct RoomDirectorySearchScreen: View {
|
|||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
Section {
|
Section {
|
||||||
ForEach(context.viewState.searchResults) {
|
ForEach(context.viewState.rooms) { room in
|
||||||
RoomDirectorySearchCell(result: $0, imageProvider: context.imageProvider)
|
RoomDirectorySearchCell(result: room, imageProvider: context.imageProvider) {
|
||||||
|
context.send(viewAction: .join(roomID: room.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} footer: {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
if context.viewState.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyRectangle
|
||||||
|
.onAppear {
|
||||||
|
context.send(viewAction: .reachedBottom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,25 +60,39 @@ struct RoomDirectorySearchScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The greedy size of Rectangle can create an issue with the navigation bar when the search is highlighted, so is best to use a fixed frame instead of hidden() or EmptyView()
|
||||||
|
private var emptyRectangle: some View {
|
||||||
|
Rectangle()
|
||||||
|
.frame(width: 0, height: 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct RoomDirectorySearchScreenScreen_Previews: PreviewProvider, TestablePreview {
|
struct RoomDirectorySearchScreenScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = RoomDirectorySearchScreenViewModel(roomDirectorySearch: RoomDirectorySearchProxyMock(configuration: .init(results: [.init(id: "test_1",
|
static let viewModel: RoomDirectorySearchScreenViewModel = {
|
||||||
alias: "#test_1:example.com",
|
let results = [RoomDirectorySearchResult(id: "test_1",
|
||||||
name: "Test 1",
|
alias: "#test_1:example.com",
|
||||||
topic: "Test description 1",
|
name: "Test 1",
|
||||||
avatarURL: nil,
|
topic: "Test description 1",
|
||||||
canBeJoined: true),
|
avatarURL: nil,
|
||||||
.init(id: "test_2",
|
canBeJoined: true),
|
||||||
alias: "#test_2:example.com",
|
RoomDirectorySearchResult(id: "test_2",
|
||||||
name: "Test 2",
|
alias: "#test_2:example.com",
|
||||||
topic: "Test description 2",
|
name: "Test 2",
|
||||||
avatarURL: URL.documentsDirectory,
|
topic: nil,
|
||||||
canBeJoined: false)])),
|
avatarURL: URL.documentsDirectory,
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
canBeJoined: false)]
|
||||||
imageProvider: MockMediaProvider())
|
|
||||||
|
let roomDirectorySearchProxy = RoomDirectorySearchProxyMock(configuration: .init(results: results))
|
||||||
|
|
||||||
|
let clientProxy = ClientProxyMock(.init(roomDirectorySearchProxy: roomDirectorySearchProxy))
|
||||||
|
|
||||||
|
return RoomDirectorySearchScreenViewModel(clientProxy: clientProxy,
|
||||||
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
|
imageProvider: MockMediaProvider())
|
||||||
|
}()
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
RoomDirectorySearchScreen(context: viewModel.context)
|
RoomDirectorySearchScreen(context: viewModel.context)
|
||||||
|
|||||||
@@ -342,6 +342,19 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
return await waitForRoomSummary(with: result, name: name)
|
return await waitForRoomSummary(with: result, name: name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func joinRoom(_ roomID: String) async -> Result<Void, ClientProxyError> {
|
||||||
|
do {
|
||||||
|
let _ = try await client.joinRoomById(roomId: roomID)
|
||||||
|
|
||||||
|
// Wait for the room to appear in the room lists to avoid issues downstream
|
||||||
|
let _ = await waitForRoomSummary(with: .success(roomID), name: nil, timeout: 30)
|
||||||
|
|
||||||
|
return .success(())
|
||||||
|
} catch {
|
||||||
|
return .failure(.failedJoiningRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func uploadMedia(_ media: MediaInfo) async -> Result<String, ClientProxyError> {
|
func uploadMedia(_ media: MediaInfo) async -> Result<String, ClientProxyError> {
|
||||||
guard let mimeType = media.mimeType else { return .failure(ClientProxyError.mediaFileError) }
|
guard let mimeType = media.mimeType else { return .failure(ClientProxyError.mediaFileError) }
|
||||||
do {
|
do {
|
||||||
@@ -356,8 +369,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Await the room to be available in the room summary list
|
/// Await the room to be available in the room summary list
|
||||||
/// - Parameter result: the result of a room creation Task with the `roomID`.
|
private func waitForRoomSummary(with result: Result<String, ClientProxyError>, name: String?, timeout: Int = 10) async -> Result<String, ClientProxyError> {
|
||||||
private func waitForRoomSummary(with result: Result<String, ClientProxyError>, name: String?) async -> Result<String, ClientProxyError> {
|
|
||||||
guard case .success(let roomID) = result else { return result }
|
guard case .success(let roomID) = result else { return result }
|
||||||
let runner = ExpiringTaskRunner { [weak self] in
|
let runner = ExpiringTaskRunner { [weak self] in
|
||||||
guard let roomLists = self?.roomSummaryProvider?.roomListPublisher.values else {
|
guard let roomLists = self?.roomSummaryProvider?.roomListPublisher.values else {
|
||||||
@@ -373,8 +385,8 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want to ignore the timeout error, and return the .success case because the room it was properly created already, we are only waiting for it to appear
|
// we want to ignore the timeout error, and return the .success case because the room was properly created/joined already, we are only waiting for it to appear
|
||||||
try? await runner.run(timeout: .seconds(10))
|
try? await runner.run(timeout: .seconds(timeout))
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ enum ClientProxyError: Error {
|
|||||||
case failedCheckingIsLastDevice(Error?)
|
case failedCheckingIsLastDevice(Error?)
|
||||||
case failedIgnoringUser
|
case failedIgnoringUser
|
||||||
case failedUnignoringUser
|
case failedUnignoringUser
|
||||||
|
case failedJoiningRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SlidingSyncConstants {
|
enum SlidingSyncConstants {
|
||||||
@@ -126,6 +127,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
func createRoom(name: String, topic: String?, isRoomPrivate: Bool, userIDs: [String], avatarURL: URL?) async -> Result<String, ClientProxyError>
|
func createRoom(name: String, topic: String?, isRoomPrivate: Bool, userIDs: [String], avatarURL: URL?) async -> Result<String, ClientProxyError>
|
||||||
|
|
||||||
|
func joinRoom(_ roomID: String) async -> Result<Void, ClientProxyError>
|
||||||
|
|
||||||
func uploadMedia(_ media: MediaInfo) async -> Result<String, ClientProxyError>
|
func uploadMedia(_ media: MediaInfo) async -> Result<String, ClientProxyError>
|
||||||
|
|
||||||
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
|
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b4b0603f35bb8a828beff63268191992fab0d83660e2a0c5175e98b62e7b0730
|
oid sha256:f31e5f39b635c8e4d63934933aae0bf25c7ed33d6b0f3d0bb9105cd86f0a6898
|
||||||
size 108648
|
size 111743
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2b8ce266191dfbc6e95a944ff0edc826d71cdf923f3eb0efb5e34215925fbcff
|
oid sha256:d3331568faafd4c05eb768f5bf4be9b119ebd87be1f8829f1c84465aaf2aea1f
|
||||||
size 121551
|
size 126969
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:615968dc9a95fbdb89e1f9cb3fe8b44916c3af6e323cd92c2f4d5db4ddb1284d
|
oid sha256:2d79c4687de4aae775d00f1442ae37697967f8b7d449eb4730a838e16adfb7be
|
||||||
size 64240
|
size 66836
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:97564fa372abeae5d8968dfc37dc9daefc8ae2d2fd5e3d7749d10cf0da5e4815
|
oid sha256:9cf8c4e574758e34a7e4dee7bf1fa4f9c414a37c6c0558073db9a2fd555e32dc
|
||||||
size 72907
|
size 77410
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Implement public room search list pagination and room joining
|
||||||
Reference in New Issue
Block a user