diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.swift new file mode 100644 index 000000000..94917b973 --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.swift @@ -0,0 +1,32 @@ +// +// 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 UIKit +import Reusable + +class PaginationLoadingViewCell: UITableViewCell, NibReusable, Themable { + + // MARK: - Properties + + @IBOutlet var activityIndicator: UIActivityIndicatorView! + + // MARK: - Public + + func update(theme: Theme) { + self.activityIndicator.tintColor = theme.colors.tertiaryContent + self.activityIndicator.startAnimating() + } +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.xib b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.xib new file mode 100644 index 000000000..28533154f --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.xib @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift index 664493c2e..fc04d7473 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift @@ -41,10 +41,11 @@ final class SpaceExploreRoomViewController: UIViewController { private var titleView: MainTitleView! private var emptyView: RootTabEmptyView! private var plusButtonImageView: UIImageView! + private var hasMore: Bool = false private var itemDataList: [SpaceExploreRoomListItemViewData] = [] { didSet { - tableView.reloadData() + self.tableView.reloadData() } } @@ -168,6 +169,7 @@ final class SpaceExploreRoomViewController: UIViewController { self.tableView.allowsSelection = true self.tableView.register(cellType: SpaceChildViewCell.self) self.tableView.register(cellType: SpaceChildSpaceViewCell.self) + self.tableView.register(cellType: PaginationLoadingViewCell.self) self.tableView.tableFooterView = UIView() } @@ -177,7 +179,8 @@ final class SpaceExploreRoomViewController: UIViewController { self.renderLoading() case .spaceNameFound(let spaceName): self.titleView.subtitleLabel.text = spaceName - case .loaded(let children): + case .loaded(let children, let hasMore): + self.hasMore = hasMore self.renderLoaded(children: children) case .emptySpace: self.renderEmptySpace() @@ -240,10 +243,16 @@ extension SpaceExploreRoomViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.itemDataList.count + return self.itemDataList.count + (self.hasMore ? 1 : 0) } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard indexPath.row < self.itemDataList.count else { + let cell = tableView.dequeueReusableCell(for: indexPath, cellType: PaginationLoadingViewCell.self) + cell.update(theme: self.theme) + return cell + } + let viewData = self.itemDataList[indexPath.row] let cell = viewData.childInfo.roomType == .space ? tableView.dequeueReusableCell(for: indexPath, cellType: SpaceChildSpaceViewCell.self) : tableView.dequeueReusableCell(for: indexPath, cellType: SpaceChildViewCell.self) @@ -262,6 +271,12 @@ extension SpaceExploreRoomViewController: UITableViewDelegate { tableView.deselectRow(at: indexPath, animated: true) self.viewModel.process(viewAction: .complete(self.itemDataList[indexPath.row], tableView.cellForRow(at: indexPath))) } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if self.hasMore && indexPath.row >= self.itemDataList.count { + self.viewModel.process(viewAction: .loadData) + } + } } // MARK: - SpaceExploreRoomViewModelViewDelegate diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift index 349530bb9..c9f54491c 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift @@ -29,13 +29,15 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType { private let spaceName: String? private var currentOperation: MXHTTPOperation? + private var nextBatch: String? + private var rootSpaceChildInfo: MXSpaceChildInfo? private var itemDataList: [SpaceExploreRoomListItemViewData] = [] { didSet { self.updateFilteredItemList() } } - private var searchKeyword: String? = nil { + private var searchKeyword: String? { didSet { self.updateFilteredItemList() } @@ -49,7 +51,7 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType { self.update(viewState: .emptyFilterResult) } } else { - self.update(viewState: .loaded(self.filteredItemDataList)) + self.update(viewState: .loaded(self.filteredItemDataList, self.nextBatch != nil && (self.searchKeyword ?? "").isEmpty)) } } } @@ -90,32 +92,51 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType { // MARK: - Private private func loadData() { + guard self.currentOperation == nil else { + return + } + if let spaceName = self.spaceName { self.update(viewState: .spaceNameFound(spaceName)) } - self.update(viewState: .loading) + if self.nextBatch == nil { + self.update(viewState: .loading) + } - self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: nil, completion: { [weak self] response in + self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: nil, maxDepth: 1, paginationToken: self.nextBatch, completion: { [weak self] response in guard let self = self else { return } switch response { case .success(let spaceSummary): - self.itemDataList = spaceSummary.childInfos.compactMap({ childInfo in - guard childInfo.parentIds.contains(self.spaceId) else { + self.nextBatch = spaceSummary.nextBatch + // The MXSpaceChildInfo of the root space is available only in the first batch + if let rootSpaceInfo = spaceSummary.spaceInfo { + self.rootSpaceChildInfo = rootSpaceInfo + } + + let batchedItemDataList: [SpaceExploreRoomListItemViewData] = spaceSummary.childInfos.compactMap({ childInfo in + guard let rootSpaceInfo = self.rootSpaceChildInfo, rootSpaceInfo.childrenIds.contains(childInfo.childRoomId) else { return nil } - let avatarViewData = AvatarViewData(matrixItemId: childInfo.childRoomId, displayName: childInfo.displayName, avatarUrl: childInfo.avatarUrl, mediaManager: self.session.mediaManager, fallbackImage: .matrixItem(childInfo.childRoomId, childInfo.name)) + let avatarViewData = AvatarViewData(matrixItemId: childInfo.childRoomId, + displayName: childInfo.displayName, + avatarUrl: childInfo.avatarUrl, + mediaManager: self.session.mediaManager, + fallbackImage: .matrixItem(childInfo.childRoomId, childInfo.name)) return SpaceExploreRoomListItemViewData(childInfo: childInfo, avatarViewData: avatarViewData) }).sorted(by: { item1, item2 in return !item2.childInfo.suggested || item1.childInfo.suggested }) + self.itemDataList.append(contentsOf: batchedItemDataList) case .failure(let error): self.update(viewState: .error(error)) } + + self.currentOperation = nil }) } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewState.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewState.swift index c7c8ae50e..52203c190 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewState.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewState.swift @@ -22,7 +22,7 @@ import Foundation enum SpaceExploreRoomViewState { case loading case spaceNameFound(_ spaceName: String) - case loaded(_ children: [SpaceExploreRoomListItemViewData]) + case loaded(_ children: [SpaceExploreRoomListItemViewData], _ hasMore: Bool) case emptySpace case emptyFilterResult case error(Error) diff --git a/changelog.d/4893.feature b/changelog.d/4893.feature new file mode 100644 index 000000000..1cdb014be --- /dev/null +++ b/changelog.d/4893.feature @@ -0,0 +1 @@ +SpaceExploreRoomViewModel: Support pagination in the Space Summary API \ No newline at end of file