From b34418e32815e8be0e9196508f0cf3228691f873 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Tue, 12 Oct 2021 20:48:30 +0200 Subject: [PATCH 1/4] Support pagination in the Space Summary API --- .../SpaceChildLoadingViewCell.swift | 45 ++++++++++++++++++ .../ExploreRoom/SpaceChildLoadingViewCell.xib | 47 +++++++++++++++++++ .../SpaceExploreRoomViewController.swift | 21 +++++++-- .../SpaceExploreRoomViewModel.swift | 35 +++++++++++--- .../SpaceExploreRoomViewState.swift | 2 +- changelog.d/4893.feature | 1 + 6 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift create mode 100644 Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib create mode 100644 changelog.d/4893.feature diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift new file mode 100644 index 000000000..e70a9565e --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift @@ -0,0 +1,45 @@ +// +// 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 SpaceChildLoadingViewCell: UITableViewCell, NibReusable, Themable { + + // MARK: - Properties + + @IBOutlet var circleProgressView: CircleProgressView! + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + setupViews() + } + + // MARK: - Public + + func update(theme: Theme) { + self.circleProgressView.lineColor = theme.colors.tertiaryContent + } + + // MARK: - Private + + private func setupViews() { + self.circleProgressView.startAnimating() + } +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib new file mode 100644 index 000000000..84fc0e1fe --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift index 664493c2e..28a64b161 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: SpaceChildLoadingViewCell.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: SpaceChildLoadingViewCell.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..171e8084e 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, from: 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 From 49b48b3e05303ce6253802423ff60e752445f559 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Tue, 26 Oct 2021 14:40:51 +0200 Subject: [PATCH 2/4] Support pagination in the Space Summary API - Move back to default iOS activity indicator --- .../SpaceChildLoadingViewCell.swift | 19 +++----------- .../ExploreRoom/SpaceChildLoadingViewCell.xib | 26 ++++++------------- .../SpaceExploreRoomViewModel.swift | 2 +- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift index e70a9565e..873241572 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift @@ -21,25 +21,12 @@ class SpaceChildLoadingViewCell: UITableViewCell, NibReusable, Themable { // MARK: - Properties - @IBOutlet var circleProgressView: CircleProgressView! + @IBOutlet var activityIndicator: UIActivityIndicatorView! - // MARK: - Life cycle - - override func awakeFromNib() { - super.awakeFromNib() - - setupViews() - } - // MARK: - Public func update(theme: Theme) { - self.circleProgressView.lineColor = theme.colors.tertiaryContent - } - - // MARK: - Private - - private func setupViews() { - self.circleProgressView.startAnimating() + self.activityIndicator.tintColor = theme.colors.tertiaryContent + self.activityIndicator.startAnimating() } } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib index 84fc0e1fe..a49cc20a8 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib @@ -13,33 +13,23 @@ - + - - - - - - - - - - - - - + + + - - - + + + - + diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift index 171e8084e..7205f8f2e 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift @@ -104,7 +104,7 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType { self.update(viewState: .loading) } - self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: nil, maxDepth: 1, from: self.nextBatch, completion: { [weak self] response in + self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: 20, maxDepth: 1, from: nil, completion: { [weak self] response in guard let self = self else { return } From d413c4310f7176e6ff12116934c0882b86946ede Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Tue, 26 Oct 2021 14:44:51 +0200 Subject: [PATCH 3/4] Support pagination in the Space Summary API - Revert test tweaks --- .../SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift index 7205f8f2e..171e8084e 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift @@ -104,7 +104,7 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType { self.update(viewState: .loading) } - self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: 20, maxDepth: 1, from: nil, completion: { [weak self] response in + self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: nil, maxDepth: 1, from: self.nextBatch, completion: { [weak self] response in guard let self = self else { return } From 5ecfc32aceae8f3dbb9a9056c791075cde5382a2 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Wed, 27 Oct 2021 10:27:13 +0200 Subject: [PATCH 4/4] Support pagination in the Space Summary API - Update after review --- ...dLoadingViewCell.swift => PaginationLoadingViewCell.swift} | 2 +- ...ChildLoadingViewCell.xib => PaginationLoadingViewCell.xib} | 2 +- .../ExploreRoom/SpaceExploreRoomViewController.swift | 4 ++-- .../SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/{SpaceChildLoadingViewCell.swift => PaginationLoadingViewCell.swift} (93%) rename Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/{SpaceChildLoadingViewCell.xib => PaginationLoadingViewCell.xib} (97%) diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.swift similarity index 93% rename from Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift rename to Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.swift index 873241572..94917b973 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.swift @@ -17,7 +17,7 @@ import UIKit import Reusable -class SpaceChildLoadingViewCell: UITableViewCell, NibReusable, Themable { +class PaginationLoadingViewCell: UITableViewCell, NibReusable, Themable { // MARK: - Properties diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.xib similarity index 97% rename from Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib rename to Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.xib index a49cc20a8..28533154f 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceChildLoadingViewCell.xib +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/PaginationLoadingViewCell.xib @@ -10,7 +10,7 @@ - + diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift index 28a64b161..fc04d7473 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewController.swift @@ -169,7 +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: SpaceChildLoadingViewCell.self) + self.tableView.register(cellType: PaginationLoadingViewCell.self) self.tableView.tableFooterView = UIView() } @@ -248,7 +248,7 @@ extension SpaceExploreRoomViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard indexPath.row < self.itemDataList.count else { - let cell = tableView.dequeueReusableCell(for: indexPath, cellType: SpaceChildLoadingViewCell.self) + let cell = tableView.dequeueReusableCell(for: indexPath, cellType: PaginationLoadingViewCell.self) cell.update(theme: self.theme) return cell } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift index 171e8084e..c9f54491c 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/SpaceExploreRoomViewModel.swift @@ -104,7 +104,7 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType { self.update(viewState: .loading) } - self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: nil, maxDepth: 1, from: self.nextBatch, 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 }