mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-25 19:10:49 +02:00
ShowDirectory: Update view model and handle direct input of room id alias even non public.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PublicRoomsDirectoryViewModel {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let dataSource: PublicRoomsDirectoryDataSource
|
||||
private let session: MXSession
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var roomsCount: Int {
|
||||
return Int(dataSource.roomsCount)
|
||||
}
|
||||
|
||||
var directoryServerDisplayname: String? {
|
||||
return dataSource.directoryServerDisplayname
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(dataSource: PublicRoomsDirectoryDataSource, session: MXSession) {
|
||||
self.dataSource = dataSource
|
||||
self.session = session
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func roomViewModel(at row: Int) -> DirectoryRoomTableViewCellVM? {
|
||||
self.roomViewModel(at: IndexPath(row: row, section: 0))
|
||||
}
|
||||
|
||||
func roomViewModel(at indexPath: IndexPath) -> DirectoryRoomTableViewCellVM? {
|
||||
guard let publicRoom = dataSource.room(at: indexPath) else { return nil }
|
||||
return self.roomCellViewModel(with: publicRoom)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func roomCellViewModel(with publicRoom: MXPublicRoom) -> DirectoryRoomTableViewCellVM {
|
||||
let summary = session.roomSummary(withRoomId: publicRoom.roomId)
|
||||
|
||||
return DirectoryRoomTableViewCellVM(title: publicRoom.displayname(),
|
||||
numberOfUsers: publicRoom.numJoinedMembers,
|
||||
subtitle: MXTools.stripNewlineCharacters(publicRoom.topic),
|
||||
isJoined: summary?.membership == .join,
|
||||
roomId: publicRoom.roomId,
|
||||
avatarUrl: publicRoom.avatarUrl,
|
||||
mediaManager: session.mediaManager)
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,8 @@ final class ShowDirectoryViewController: UIViewController {
|
||||
bar.delegate = self
|
||||
return bar
|
||||
}()
|
||||
|
||||
private var sections: [ShowDirectorySection] = []
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@@ -171,10 +173,12 @@ final class ShowDirectoryViewController: UIViewController {
|
||||
switch viewState {
|
||||
case .loading:
|
||||
self.renderLoading()
|
||||
case .loaded:
|
||||
self.renderLoaded()
|
||||
case .loaded(let sections):
|
||||
self.renderLoaded(sections: sections)
|
||||
case .error(let error):
|
||||
self.render(error: error)
|
||||
case .loadedWithoutUpdate:
|
||||
self.renderLoadedWithoutUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,14 +186,22 @@ final class ShowDirectoryViewController: UIViewController {
|
||||
addSpinnerFooterView()
|
||||
}
|
||||
|
||||
private func renderLoaded() {
|
||||
private func renderLoaded(sections: [ShowDirectorySection]) {
|
||||
removeSpinnerFooterView()
|
||||
self.sections = sections
|
||||
self.mainTableView.reloadData()
|
||||
}
|
||||
|
||||
private func renderLoadedWithoutUpdate() {
|
||||
removeSpinnerFooterView()
|
||||
|
||||
}
|
||||
|
||||
private func render(error: Error) {
|
||||
removeSpinnerFooterView()
|
||||
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
|
||||
self.errorPresenter.presentError(from: self, forError: error, animated: true) {
|
||||
// If the join failed, reload the table view
|
||||
self.mainTableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
@@ -209,17 +221,37 @@ final class ShowDirectoryViewController: UIViewController {
|
||||
extension ShowDirectoryViewController: UITableViewDataSource {
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 1
|
||||
return self.sections.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return viewModel.roomsCount
|
||||
|
||||
let directorySection = self.sections[section]
|
||||
|
||||
switch directorySection {
|
||||
case .searchInput:
|
||||
return 1
|
||||
case.publicRoomsDirectory(let viewModel):
|
||||
return viewModel.roomsCount
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let section = self.sections[indexPath.section]
|
||||
|
||||
let cellViewModel: DirectoryRoomTableViewCellVM?
|
||||
|
||||
switch section {
|
||||
case .searchInput(let searchInputViewData):
|
||||
cellViewModel = searchInputViewData
|
||||
case.publicRoomsDirectory(let viewModel):
|
||||
cellViewModel = viewModel.roomViewModel(at: indexPath.row)
|
||||
}
|
||||
|
||||
let cell: DirectoryRoomTableViewCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
if let viewModel = viewModel.roomViewModel(at: indexPath) {
|
||||
cell.configure(withViewModel: viewModel)
|
||||
if let cellViewModel = cellViewModel {
|
||||
cell.configure(withViewModel: cellViewModel)
|
||||
}
|
||||
cell.indexPath = indexPath
|
||||
cell.delegate = self
|
||||
@@ -255,16 +287,28 @@ extension ShowDirectoryViewController: UITableViewDelegate {
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
guard let view: DirectoryNetworkTableHeaderFooterView = tableView.dequeueReusableHeaderFooterView() else {
|
||||
return nil
|
||||
|
||||
let sectionHeaderView: UIView?
|
||||
|
||||
let directorySection = self.sections[section]
|
||||
|
||||
switch directorySection {
|
||||
case .searchInput:
|
||||
sectionHeaderView = nil
|
||||
case .publicRoomsDirectory(let viewModel):
|
||||
guard let view: DirectoryNetworkTableHeaderFooterView = tableView.dequeueReusableHeaderFooterView() else {
|
||||
return nil
|
||||
}
|
||||
if let name = viewModel.directoryServerDisplayname {
|
||||
let title = VectorL10n.searchableDirectoryXNetwork(name)
|
||||
view.configure(withViewModel: DirectoryNetworkVM(title: title))
|
||||
}
|
||||
view.update(theme: self.theme)
|
||||
view.delegate = self
|
||||
sectionHeaderView = view
|
||||
}
|
||||
if let name = self.viewModel.directoryServerDisplayname {
|
||||
let title = VectorL10n.searchableDirectoryXNetwork(name)
|
||||
view.configure(withViewModel: DirectoryNetworkVM(title: title))
|
||||
}
|
||||
view.update(theme: self.theme)
|
||||
view.delegate = self
|
||||
return view
|
||||
|
||||
return sectionHeaderView
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
@@ -311,8 +355,4 @@ extension ShowDirectoryViewController: ShowDirectoryViewModelViewDelegate {
|
||||
func showDirectoryViewModel(_ viewModel: ShowDirectoryViewModelType, didUpdateViewState viewSate: ShowDirectoryViewState) {
|
||||
self.render(viewState: viewSate)
|
||||
}
|
||||
|
||||
func showDirectoryViewModelDidUpdateDataSource(_ viewModel: ShowDirectoryViewModelType) {
|
||||
self.mainTableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ShowDirectorySection {
|
||||
case searchInput(_ searchInputViewData: DirectoryRoomTableViewCellVM)
|
||||
case publicRoomsDirectory(_ viewModel: PublicRoomsDirectoryViewModel)
|
||||
}
|
||||
|
||||
final class ShowDirectoryViewModel: NSObject, ShowDirectoryViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
@@ -27,38 +32,30 @@ final class ShowDirectoryViewModel: NSObject, ShowDirectoryViewModelType {
|
||||
private let session: MXSession
|
||||
private let dataSource: PublicRoomsDirectoryDataSource
|
||||
|
||||
private let publicRoomsDirectoryViewModel: PublicRoomsDirectoryViewModel
|
||||
|
||||
private var currentOperation: MXHTTPOperation?
|
||||
private var userDisplayName: String?
|
||||
private var sections: [ShowDirectorySection] = []
|
||||
|
||||
private var canPaginatePublicRoomsDirectory: Bool {
|
||||
return !dataSource.hasReachedPaginationEnd && currentOperation == nil
|
||||
}
|
||||
|
||||
private var publicRoomsDirectorySection: ShowDirectorySection {
|
||||
return .publicRoomsDirectory(self.publicRoomsDirectoryViewModel)
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
weak var viewDelegate: ShowDirectoryViewModelViewDelegate?
|
||||
weak var coordinatorDelegate: ShowDirectoryViewModelCoordinatorDelegate?
|
||||
|
||||
var roomsCount: Int {
|
||||
return Int(dataSource.roomsCount)
|
||||
}
|
||||
var directoryServerDisplayname: String? {
|
||||
return dataSource.directoryServerDisplayname
|
||||
}
|
||||
func roomViewModel(at indexPath: IndexPath) -> DirectoryRoomTableViewCellVM? {
|
||||
guard let room = dataSource.room(at: indexPath) else { return nil }
|
||||
let summary = session.roomSummary(withRoomId: room.roomId)
|
||||
|
||||
return DirectoryRoomTableViewCellVM(title: room.displayname(),
|
||||
numberOfUsers: room.numJoinedMembers,
|
||||
subtitle: MXTools.stripNewlineCharacters(room.topic),
|
||||
isJoined: summary?.membership == .join,
|
||||
roomId: room.roomId,
|
||||
avatarUrl: room.avatarUrl,
|
||||
mediaManager: session.mediaManager)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(session: MXSession, dataSource: PublicRoomsDirectoryDataSource) {
|
||||
self.session = session
|
||||
self.dataSource = dataSource
|
||||
self.publicRoomsDirectoryViewModel = PublicRoomsDirectoryViewModel(dataSource: dataSource, session: session)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -69,17 +66,38 @@ final class ShowDirectoryViewModel: NSObject, ShowDirectoryViewModelType {
|
||||
|
||||
func process(viewAction: ShowDirectoryViewAction) {
|
||||
switch viewAction {
|
||||
case .loadData(let force):
|
||||
self.loadData(force: force)
|
||||
case .loadData:
|
||||
self.resetSections()
|
||||
self.paginatePublicRoomsDirectory(force: false)
|
||||
case .selectRoom(let indexPath):
|
||||
guard let room = dataSource.room(at: indexPath) else { return }
|
||||
self.coordinatorDelegate?.showDirectoryViewModelDidSelect(self, room: room)
|
||||
|
||||
let directorySection = self.sections[indexPath.section]
|
||||
|
||||
switch directorySection {
|
||||
case .searchInput:
|
||||
break
|
||||
case.publicRoomsDirectory:
|
||||
guard let publicRoom = dataSource.room(at: indexPath) else { return }
|
||||
self.coordinatorDelegate?.showDirectoryViewModelDidSelect(self, room: publicRoom)
|
||||
}
|
||||
case .joinRoom(let indexPath):
|
||||
guard let room = dataSource.room(at: indexPath) else { return }
|
||||
joinRoom(room)
|
||||
|
||||
let directorySection = self.sections[indexPath.section]
|
||||
let roomIdOrAlias: String?
|
||||
|
||||
switch directorySection {
|
||||
case .searchInput(let searchInputViewData):
|
||||
roomIdOrAlias = searchInputViewData.title
|
||||
case .publicRoomsDirectory:
|
||||
let publicRoom = dataSource.room(at: IndexPath(row: indexPath.row, section: 0))
|
||||
roomIdOrAlias = publicRoom?.roomId
|
||||
}
|
||||
|
||||
if let roomIdOrAlias = roomIdOrAlias {
|
||||
joinRoom(withRoomIdOrAlias: roomIdOrAlias)
|
||||
}
|
||||
case .search(let pattern):
|
||||
self.dataSource.searchPattern = pattern
|
||||
self.loadData(force: true)
|
||||
self.search(with: pattern)
|
||||
case .createNewRoom:
|
||||
self.coordinatorDelegate?.showDirectoryViewModelDidTapCreateNewRoom(self)
|
||||
case .switchServer:
|
||||
@@ -90,10 +108,22 @@ final class ShowDirectoryViewModel: NSObject, ShowDirectoryViewModelType {
|
||||
}
|
||||
}
|
||||
|
||||
func updatePublicRoomsDataSource(with cellData: MXKDirectoryServerCellDataStoring) {
|
||||
if let thirdpartyProtocolInstance = cellData.thirdPartyProtocolInstance {
|
||||
self.dataSource.thirdpartyProtocolInstance = thirdpartyProtocolInstance
|
||||
} else if let homeserver = cellData.homeserver {
|
||||
self.dataSource.includeAllNetworks = cellData.includeAllNetworks
|
||||
self.dataSource.homeserver = homeserver
|
||||
}
|
||||
|
||||
self.resetSections()
|
||||
self.paginatePublicRoomsDirectory(force: false)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadData(force: Bool) {
|
||||
if !force && (dataSource.hasReachedPaginationEnd || currentOperation != nil) {
|
||||
private func paginatePublicRoomsDirectory(force: Bool) {
|
||||
if !force && !self.canPaginatePublicRoomsDirectory {
|
||||
// We got all public rooms or we are already paginating
|
||||
// Do nothing
|
||||
return
|
||||
@@ -101,12 +131,16 @@ final class ShowDirectoryViewModel: NSObject, ShowDirectoryViewModelType {
|
||||
|
||||
self.update(viewState: .loading)
|
||||
|
||||
// Useful only when force is true
|
||||
self.cancelOperations()
|
||||
|
||||
currentOperation = dataSource.paginate({ [weak self] (roomsAdded) in
|
||||
guard let self = self else { return }
|
||||
if roomsAdded > 0 {
|
||||
self.viewDelegate?.showDirectoryViewModelDidUpdateDataSource(self)
|
||||
self.update(viewState: .loaded(self.sections))
|
||||
} else {
|
||||
self.update(viewState: .loadedWithoutUpdate)
|
||||
}
|
||||
self.update(viewState: .loaded)
|
||||
self.currentOperation = nil
|
||||
}, failure: { [weak self] (error) in
|
||||
guard let self = self else { return }
|
||||
@@ -116,6 +150,12 @@ final class ShowDirectoryViewModel: NSObject, ShowDirectoryViewModelType {
|
||||
})
|
||||
}
|
||||
|
||||
private func resetSections() {
|
||||
self.sections = [self.publicRoomsDirectorySection]
|
||||
}
|
||||
|
||||
// FIXME: DirectoryServerPickerViewController should be instantiated from ShowDirectoryCoordinator
|
||||
// It should be just a call like: self.coordinatorDelegate?.showDirectoryServerPicker(self)
|
||||
private func switchServer() {
|
||||
let controller = DirectoryServerPickerViewController()
|
||||
let source = MXKDirectoryServersDataSource(matrixSession: session)
|
||||
@@ -126,26 +166,85 @@ final class ShowDirectoryViewModel: NSObject, ShowDirectoryViewModelType {
|
||||
guard let self = self else { return }
|
||||
guard let cellData = cellData else { return }
|
||||
|
||||
if let thirdpartyProtocolInstance = cellData.thirdPartyProtocolInstance {
|
||||
self.dataSource.thirdpartyProtocolInstance = thirdpartyProtocolInstance
|
||||
} else if let homeserver = cellData.homeserver {
|
||||
self.dataSource.includeAllNetworks = cellData.includeAllNetworks
|
||||
self.dataSource.homeserver = homeserver
|
||||
}
|
||||
|
||||
self.loadData(force: false)
|
||||
self.updatePublicRoomsDataSource(with: cellData)
|
||||
}
|
||||
|
||||
self.coordinatorDelegate?.showDirectoryViewModelWantsToShow(self, controller: controller)
|
||||
}
|
||||
|
||||
private func joinRoom(_ room: MXPublicRoom) {
|
||||
session.joinRoom(room.roomId) { [weak self] (response) in
|
||||
private func joinRoom(withRoomIdOrAlias roomIdOrAlias: String) {
|
||||
session.joinRoom(roomIdOrAlias) { [weak self] (response) in
|
||||
guard let self = self else { return }
|
||||
self.viewDelegate?.showDirectoryViewModelDidUpdateDataSource(self)
|
||||
switch response {
|
||||
case .success:
|
||||
self.update(viewState: .loaded(self.sections))
|
||||
case .failure(let error):
|
||||
self.update(viewState: .error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func search(with pattern: String?) {
|
||||
self.dataSource.searchPattern = pattern
|
||||
|
||||
var sections: [ShowDirectorySection] = []
|
||||
|
||||
var shouldUpdate = false
|
||||
|
||||
// If the search text is a room id or alias we add search input entry in sections
|
||||
if let searchText = pattern, let searchInputViewData = self.searchInputViewData(from: searchText) {
|
||||
sections.append(.searchInput(searchInputViewData))
|
||||
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
sections.append(self.publicRoomsDirectorySection)
|
||||
|
||||
self.sections = sections
|
||||
|
||||
if shouldUpdate {
|
||||
self.update(viewState: .loaded(self.sections))
|
||||
}
|
||||
|
||||
self.paginatePublicRoomsDirectory(force: true)
|
||||
}
|
||||
|
||||
private func searchInputViewData(from searchText: String) -> DirectoryRoomTableViewCellVM? {
|
||||
guard MXTools.isMatrixRoomAlias(searchText) || MXTools.isMatrixRoomIdentifier(searchText) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let roomIdOrAlias = searchText
|
||||
|
||||
let searchInputViewData: DirectoryRoomTableViewCellVM
|
||||
|
||||
if let room = self.session.vc_room(withIdOrAlias: roomIdOrAlias) {
|
||||
searchInputViewData = self.roomCellViewModel(with: room)
|
||||
} else {
|
||||
searchInputViewData = self.roomCellViewModel(with: roomIdOrAlias)
|
||||
}
|
||||
|
||||
return searchInputViewData
|
||||
}
|
||||
|
||||
private func roomCellViewModel(with room: MXRoom) -> DirectoryRoomTableViewCellVM {
|
||||
let displayName = room.summary.displayname
|
||||
let joinedMembersCount = Int(room.summary.membersCount.joined)
|
||||
let topic = MXTools.stripNewlineCharacters(room.summary.topic)
|
||||
let isJoined = room.summary.membership == .join
|
||||
let avatarStringUrl = room.summary.avatar
|
||||
let mediaManager = self.session.mediaManager
|
||||
|
||||
return DirectoryRoomTableViewCellVM(title: displayName, numberOfUsers: joinedMembersCount, subtitle: topic, isJoined: isJoined, roomId: room.roomId, avatarUrl: avatarStringUrl, mediaManager: mediaManager)
|
||||
}
|
||||
|
||||
private func roomCellViewModel(with roomIdOrAlias: String) -> DirectoryRoomTableViewCellVM {
|
||||
let displayName = roomIdOrAlias
|
||||
let mediaManager = self.session.mediaManager
|
||||
|
||||
return DirectoryRoomTableViewCellVM(title: displayName, numberOfUsers: 0, subtitle: nil, isJoined: false, roomId: roomIdOrAlias, avatarUrl: nil, mediaManager: mediaManager)
|
||||
}
|
||||
|
||||
private func update(viewState: ShowDirectoryViewState) {
|
||||
self.viewDelegate?.showDirectoryViewModel(self, didUpdateViewState: viewState)
|
||||
}
|
||||
@@ -172,7 +271,7 @@ extension ShowDirectoryViewModel: MXKDataSourceDelegate {
|
||||
}
|
||||
|
||||
func dataSource(_ dataSource: MXKDataSource!, didStateChange state: MXKDataSourceState) {
|
||||
self.viewDelegate?.showDirectoryViewModelDidUpdateDataSource(self)
|
||||
self.update(viewState: .loaded(self.sections))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import Foundation
|
||||
|
||||
protocol ShowDirectoryViewModelViewDelegate: class {
|
||||
func showDirectoryViewModel(_ viewModel: ShowDirectoryViewModelType, didUpdateViewState viewSate: ShowDirectoryViewState)
|
||||
func showDirectoryViewModelDidUpdateDataSource(_ viewModel: ShowDirectoryViewModelType)
|
||||
}
|
||||
|
||||
protocol ShowDirectoryViewModelCoordinatorDelegate: class {
|
||||
@@ -38,7 +37,5 @@ protocol ShowDirectoryViewModelType {
|
||||
|
||||
func process(viewAction: ShowDirectoryViewAction)
|
||||
|
||||
var roomsCount: Int { get }
|
||||
var directoryServerDisplayname: String? { get }
|
||||
func roomViewModel(at indexPath: IndexPath) -> DirectoryRoomTableViewCellVM?
|
||||
func updatePublicRoomsDataSource(with cellData: MXKDirectoryServerCellDataStoring)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import Foundation
|
||||
/// ShowDirectoryViewController view state
|
||||
enum ShowDirectoryViewState {
|
||||
case loading
|
||||
case loaded
|
||||
case loadedWithoutUpdate
|
||||
case loaded(_ sections: [ShowDirectorySection])
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user