ShowDirectory: Update view model and handle direct input of room id alias even non public.

This commit is contained in:
SBiOSoftWhare
2021-06-11 18:10:13 +02:00
parent 87b2940fc6
commit 4e3634185f
7 changed files with 291 additions and 71 deletions
@@ -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)
}