Merge pull request #4994 from vector-im/gil/4893_support_pagination_in_the_space_summary_api

Support pagination in the Space Summary API
This commit is contained in:
Gil Eluard
2021-11-09 09:35:24 +01:00
committed by GitHub
6 changed files with 117 additions and 11 deletions
@@ -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()
}
}
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="68" id="gUf-SL-Ia7" customClass="PaginationLoadingViewCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="68"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="gUf-SL-Ia7" id="YoL-49-1Hj">
<rect key="frame" x="0.0" y="0.0" width="320" height="68"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="large" translatesAutoresizingMaskIntoConstraints="NO" id="Tld-uY-uDU">
<rect key="frame" x="141.5" y="17" width="37" height="34"/>
</activityIndicatorView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Tld-uY-uDU" secondAttribute="bottom" constant="17" id="cib-oa-sGv"/>
<constraint firstItem="Tld-uY-uDU" firstAttribute="top" secondItem="YoL-49-1Hj" secondAttribute="top" constant="17" id="fuX-uS-ylN"/>
<constraint firstItem="Tld-uY-uDU" firstAttribute="centerX" secondItem="YoL-49-1Hj" secondAttribute="centerX" id="ots-Sg-fHU"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="wBe-er-msB"/>
<connections>
<outlet property="activityIndicator" destination="Tld-uY-uDU" id="mq3-lG-y9D"/>
</connections>
<point key="canvasLocation" x="137.68115942028987" y="107.14285714285714"/>
</tableViewCell>
</objects>
</document>
@@ -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
@@ -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
})
}
@@ -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)