Add paginated panel loading
This commit is contained in:
@@ -8,6 +8,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
private var rows: [IssueRow] = []
|
||||
private var filterOptions: IssueFilterOptions?
|
||||
private var filterTask: Task<Void, Never>?
|
||||
private var currentPage: UInt32 = 0
|
||||
private lazy var filterButton = UIBarButtonItem(
|
||||
image: context.symbol("line.3.horizontal.decrease.circle")
|
||||
)
|
||||
@@ -45,17 +46,38 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
private func loadIssues(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadIssues(page: 1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadIssues(page: currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadIssues(page: UInt32, refreshing: Bool) {
|
||||
if page == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
rows = try await context.core.issues(owner: owner, repository: repository)
|
||||
let result = try await context.core.issues(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
page: page
|
||||
)
|
||||
if page == 1 { rows = result.rows } else { rows.append(contentsOf: result.rows) }
|
||||
currentPage = page
|
||||
finishPagination(hasMore: result.hasMore)
|
||||
tableView.reloadData()
|
||||
updateEmptyState()
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if page == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +357,7 @@ final class MilestonesViewController: RefreshingTableViewController {
|
||||
private let owner: String
|
||||
private let repository: String
|
||||
private var rows: [MilestoneRow] = []
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
init(context: AppContext, owner: String, repository: String) {
|
||||
self.context = context
|
||||
@@ -356,11 +379,29 @@ final class MilestonesViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
override func loadContent(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPage(1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadPage(currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPage(_ page: UInt32, refreshing: Bool) {
|
||||
if page == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
rows = try await context.core.milestones(owner: owner, repository: repository)
|
||||
let result = try await context.core.milestones(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
page: page
|
||||
)
|
||||
if page == 1 { rows = result.rows } else { rows.append(contentsOf: result.rows) }
|
||||
currentPage = page
|
||||
finishPagination(hasMore: result.hasMore)
|
||||
tableView.reloadData()
|
||||
tableView.backgroundView = rows.isEmpty
|
||||
? EmptyBackgroundView(
|
||||
@@ -369,9 +410,12 @@ final class MilestonesViewController: RefreshingTableViewController {
|
||||
)
|
||||
: nil
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if page == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +456,7 @@ final class MilestoneViewController: RefreshingTableViewController {
|
||||
private let repository: String
|
||||
private let id: Int64
|
||||
private var page: MilestonePage?
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
init(context: AppContext, owner: String, repository: String, id: Int64) {
|
||||
self.context = context
|
||||
@@ -435,21 +480,45 @@ final class MilestoneViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
override func loadContent(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPage(1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadPage(currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPage(_ requestedPage: UInt32, refreshing: Bool) {
|
||||
if requestedPage == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
page = try await context.core.milestone(
|
||||
let result = try await context.core.milestone(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
id: id
|
||||
id: id,
|
||||
page: requestedPage
|
||||
)
|
||||
if requestedPage == 1 {
|
||||
page = result
|
||||
} else {
|
||||
page?.issues.append(contentsOf: result.issues)
|
||||
page?.pulls.append(contentsOf: result.pulls)
|
||||
page?.hasMore = result.hasMore
|
||||
}
|
||||
currentPage = requestedPage
|
||||
finishPagination(hasMore: result.hasMore)
|
||||
title = page?.milestone.title
|
||||
tableView.reloadData()
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if requestedPage == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,6 +653,7 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
private var rows: [PullRow] = []
|
||||
private var filterOptions: PullFilterOptions?
|
||||
private var filterTask: Task<Void, Never>?
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
@@ -626,11 +696,25 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
private func loadPulls(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPulls(page: 1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadPulls(page: currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPulls(page: UInt32, refreshing: Bool) {
|
||||
if page == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
rows = try await context.core.pulls()
|
||||
let result = try await context.core.pulls(page: page)
|
||||
if page == 1 { rows = result.rows } else { rows.append(contentsOf: result.rows) }
|
||||
currentPage = page
|
||||
finishPagination(hasMore: result.hasMore)
|
||||
tableView.reloadData()
|
||||
let status = context.core.settings().pullStatus
|
||||
tableView.backgroundView = rows.isEmpty
|
||||
@@ -642,9 +726,12 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
)
|
||||
: nil
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if page == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,6 +870,7 @@ final class CommitsViewController: RefreshingTableViewController {
|
||||
private var contents: [RepositoryContentRow] = []
|
||||
private var branch: String?
|
||||
private var mode = Mode.history
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
init(context: AppContext, owner: String, repository: String) {
|
||||
self.context = context
|
||||
@@ -808,7 +896,19 @@ final class CommitsViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
override func loadContent(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPage(1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
guard mode == .history else { return }
|
||||
loadPage(currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPage(_ requestedPage: UInt32, refreshing: Bool) {
|
||||
if requestedPage == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
@@ -817,8 +917,11 @@ final class CommitsViewController: RefreshingTableViewController {
|
||||
page = try await context.core.commits(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
branch: branch
|
||||
branch: branch,
|
||||
pages: requestedPage
|
||||
)
|
||||
currentPage = requestedPage
|
||||
finishPagination(hasMore: page?.hasMore ?? false)
|
||||
updateBranchMenu()
|
||||
case .files:
|
||||
contents = try await context.core.repositoryContents(
|
||||
@@ -830,9 +933,12 @@ final class CommitsViewController: RefreshingTableViewController {
|
||||
tableView.reloadData()
|
||||
updateEmptyView()
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if requestedPage == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,18 @@ import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
class MarkdownPageViewController: UIViewController {
|
||||
class MarkdownPageViewController: UIViewController, UIScrollViewDelegate {
|
||||
let context: AppContext
|
||||
let scrollView = UIScrollView()
|
||||
let stack = UIStackView()
|
||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||
var loadingTask: Task<Void, Never>?
|
||||
private lazy var moreButton = UIButton(
|
||||
configuration: .plain(),
|
||||
primaryAction: UIAction { [weak self] _ in self?.requestMoreContent() }
|
||||
)
|
||||
private var hasMoreContent = false
|
||||
private var loadingMore = false
|
||||
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
@@ -25,6 +31,7 @@ class MarkdownPageViewController: UIViewController {
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.alwaysBounceVertical = true
|
||||
scrollView.delegate = self
|
||||
scrollView.refreshControl = UIRefreshControl()
|
||||
scrollView.refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged)
|
||||
view.addSubview(scrollView)
|
||||
@@ -49,6 +56,8 @@ class MarkdownPageViewController: UIViewController {
|
||||
|
||||
func loadContent(refreshing: Bool) {}
|
||||
|
||||
func loadMoreContent() {}
|
||||
|
||||
func beginLoading(refreshing: Bool) {
|
||||
if !refreshing { beginNavigationLoading(spinner) }
|
||||
}
|
||||
@@ -63,6 +72,54 @@ class MarkdownPageViewController: UIViewController {
|
||||
views.forEach(stack.addArrangedSubview)
|
||||
}
|
||||
|
||||
func resetPagination() {
|
||||
hasMoreContent = false
|
||||
loadingMore = false
|
||||
stack.removeArrangedSubview(moreButton)
|
||||
moreButton.removeFromSuperview()
|
||||
}
|
||||
|
||||
func finishPagination(hasMore: Bool) {
|
||||
hasMoreContent = hasMore
|
||||
loadingMore = false
|
||||
guard hasMore else { return }
|
||||
var configuration = moreButton.configuration
|
||||
configuration?.title = "Pull up or tap to load more"
|
||||
configuration?.showsActivityIndicator = false
|
||||
moreButton.configuration = configuration
|
||||
moreButton.accessibilityLabel = "Load more results"
|
||||
if moreButton.superview !== stack { stack.addArrangedSubview(moreButton) }
|
||||
if moreButton.constraints.isEmpty {
|
||||
moreButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
func failPagination() {
|
||||
loadingMore = false
|
||||
finishPagination(hasMore: hasMoreContent)
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard scrollView.isDragging, hasMoreContent, !loadingMore else { return }
|
||||
let bottom = max(
|
||||
-scrollView.adjustedContentInset.top,
|
||||
scrollView.contentSize.height
|
||||
+ scrollView.adjustedContentInset.bottom
|
||||
- scrollView.bounds.height
|
||||
)
|
||||
if scrollView.contentOffset.y > bottom + 60 { requestMoreContent() }
|
||||
}
|
||||
|
||||
private func requestMoreContent() {
|
||||
guard hasMoreContent, !loadingMore else { return }
|
||||
loadingMore = true
|
||||
var configuration = moreButton.configuration
|
||||
configuration?.title = "Loading more…"
|
||||
configuration?.showsActivityIndicator = true
|
||||
moreButton.configuration = configuration
|
||||
loadMoreContent()
|
||||
}
|
||||
|
||||
@objc private func refreshRequested() {
|
||||
loadContent(refreshing: true)
|
||||
}
|
||||
@@ -73,6 +130,8 @@ final class IssueViewController: MarkdownPageViewController {
|
||||
private let owner: String
|
||||
private let repository: String
|
||||
private let number: Int64
|
||||
private var page: IssuePage?
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
init(context: AppContext, owner: String, repository: String, number: Int64) {
|
||||
self.owner = owner
|
||||
@@ -110,27 +169,52 @@ final class IssueViewController: MarkdownPageViewController {
|
||||
}
|
||||
|
||||
override func loadContent(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPage(1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadPage(currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPage(_ requestedPage: UInt32, refreshing: Bool) {
|
||||
if requestedPage == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
let page = try await context.core.issue(
|
||||
let result = try await context.core.issue(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
number: number
|
||||
number: number,
|
||||
page: requestedPage
|
||||
)
|
||||
replaceContent(detailViews(
|
||||
title: page.title,
|
||||
issueState: page.state,
|
||||
meta: page.meta,
|
||||
body: page.body,
|
||||
comments: page.comments,
|
||||
milestone: page.milestone
|
||||
))
|
||||
if requestedPage == 1 {
|
||||
page = result
|
||||
} else {
|
||||
page?.comments.append(contentsOf: result.comments)
|
||||
page?.hasMore = result.hasMore
|
||||
}
|
||||
currentPage = requestedPage
|
||||
if let page {
|
||||
replaceContent(detailViews(
|
||||
title: page.title,
|
||||
issueState: page.state,
|
||||
meta: page.meta,
|
||||
body: page.body,
|
||||
comments: page.comments,
|
||||
milestone: page.milestone
|
||||
))
|
||||
finishPagination(hasMore: page.hasMore)
|
||||
}
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if requestedPage == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +224,8 @@ final class PullViewController: MarkdownPageViewController {
|
||||
private let owner: String
|
||||
private let repository: String
|
||||
private let number: Int64
|
||||
private var page: PullPage?
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
init(context: AppContext, owner: String, repository: String, number: Int64) {
|
||||
self.owner = owner
|
||||
@@ -158,24 +244,45 @@ final class PullViewController: MarkdownPageViewController {
|
||||
}
|
||||
|
||||
override func loadContent(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPage(1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadPage(currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPage(_ requestedPage: UInt32, refreshing: Bool) {
|
||||
if requestedPage == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
let page = try await context.core.pull(
|
||||
let result = try await context.core.pull(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
number: number
|
||||
number: number,
|
||||
page: requestedPage
|
||||
)
|
||||
var views = detailViews(
|
||||
title: page.title,
|
||||
meta: page.meta,
|
||||
body: page.body,
|
||||
comments: []
|
||||
)
|
||||
if !page.files.isEmpty {
|
||||
views.append(sectionLabel(page.filesRef))
|
||||
views.append(contentsOf: page.files.map { file in
|
||||
if requestedPage == 1 {
|
||||
page = result
|
||||
} else {
|
||||
page?.files.append(contentsOf: result.files)
|
||||
page?.comments.append(contentsOf: result.comments)
|
||||
page?.hasMore = result.hasMore
|
||||
}
|
||||
currentPage = requestedPage
|
||||
if let page {
|
||||
var views = detailViews(
|
||||
title: page.title,
|
||||
meta: page.meta,
|
||||
body: page.body,
|
||||
comments: []
|
||||
)
|
||||
if !page.files.isEmpty {
|
||||
views.append(sectionLabel(page.filesRef))
|
||||
views.append(contentsOf: page.files.map { file in
|
||||
detailButton(title: file.path, detail: file.status) { [weak self] in
|
||||
guard let self else { return }
|
||||
self.navigationController?.pushViewController(
|
||||
@@ -191,14 +298,19 @@ final class PullViewController: MarkdownPageViewController {
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
views.append(contentsOf: commentViews(page.comments))
|
||||
replaceContent(views)
|
||||
finishPagination(hasMore: page.hasMore)
|
||||
}
|
||||
views.append(contentsOf: commentViews(page.comments))
|
||||
replaceContent(views)
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if requestedPage == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@ final class RepositoriesViewController: RefreshingTableViewController {
|
||||
private let context: AppContext
|
||||
private let mode: RepositoryMode
|
||||
private var rows: [RepositoryRow] = []
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
init(context: AppContext, mode: RepositoryMode) {
|
||||
self.context = context
|
||||
@@ -215,11 +216,25 @@ final class RepositoriesViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
override func loadContent(refreshing: Bool) {
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPage(1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadPage(currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPage(_ page: UInt32, refreshing: Bool) {
|
||||
if page == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
rows = try await context.core.repositories()
|
||||
let result = try await context.core.repositories(page: page)
|
||||
rows = result.rows
|
||||
currentPage = page
|
||||
finishPagination(hasMore: result.hasMore)
|
||||
tableView.reloadData()
|
||||
tableView.backgroundView = rows.isEmpty
|
||||
? EmptyBackgroundView(
|
||||
@@ -228,9 +243,12 @@ final class RepositoriesViewController: RefreshingTableViewController {
|
||||
)
|
||||
: nil
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if page == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +326,7 @@ final class HomeViewController: RefreshingTableViewController {
|
||||
private let context: AppContext
|
||||
private var page: HomePage?
|
||||
private var filter = ActivityFilter.all
|
||||
private var currentPage: UInt32 = 0
|
||||
|
||||
private var activities: [ActivityRow] {
|
||||
guard let page else { return [] }
|
||||
@@ -358,24 +377,48 @@ final class HomeViewController: RefreshingTableViewController {
|
||||
refreshControl?.endRefreshing()
|
||||
return
|
||||
}
|
||||
beginLoading(refreshing: refreshing)
|
||||
loadPage(1, refreshing: refreshing)
|
||||
}
|
||||
|
||||
override func loadMoreContent() {
|
||||
loadPage(currentPage + 1, refreshing: false)
|
||||
}
|
||||
|
||||
private func loadPage(_ requestedPage: UInt32, refreshing: Bool) {
|
||||
if requestedPage == 1 {
|
||||
resetPagination()
|
||||
beginLoading(refreshing: refreshing)
|
||||
}
|
||||
loadingTask?.cancel()
|
||||
loadingTask = Task {
|
||||
do {
|
||||
page = try await context.core.home()
|
||||
let result = try await context.core.home(page: requestedPage)
|
||||
if requestedPage == 1 {
|
||||
page = result
|
||||
} else {
|
||||
page?.activities.append(contentsOf: result.activities)
|
||||
page?.issueActivities.append(contentsOf: result.issueActivities)
|
||||
page?.pullActivities.append(contentsOf: result.pullActivities)
|
||||
page?.hasMore = result.hasMore
|
||||
}
|
||||
currentPage = requestedPage
|
||||
finishPagination(hasMore: result.hasMore)
|
||||
title = page?.serverName
|
||||
tableView.tableHeaderView = page.map { page in
|
||||
if requestedPage == 1 { tableView.tableHeaderView = page.map { page in
|
||||
HeatmapView(page: page, selectedFilter: filter.rawValue) { [weak self] index in
|
||||
guard let self, let filter = ActivityFilter(rawValue: index) else { return }
|
||||
self.filter = filter
|
||||
self.updateActivities()
|
||||
}
|
||||
}
|
||||
} }
|
||||
updateActivities()
|
||||
} catch {
|
||||
if !Task.isCancelled { show(error: error) }
|
||||
if !Task.isCancelled {
|
||||
show(error: error)
|
||||
failPagination()
|
||||
}
|
||||
}
|
||||
endLoading()
|
||||
if requestedPage == 1 { endLoading() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ extension UIViewController {
|
||||
class RefreshingTableViewController: UITableViewController {
|
||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||
var loadingTask: Task<Void, Never>?
|
||||
private lazy var moreButton = UIButton(
|
||||
configuration: .plain(),
|
||||
primaryAction: UIAction { [weak self] _ in self?.requestMoreContent() }
|
||||
)
|
||||
private var hasMoreContent = false
|
||||
private var loadingMore = false
|
||||
|
||||
init() {
|
||||
super.init(style: .plain)
|
||||
@@ -46,6 +52,8 @@ class RefreshingTableViewController: UITableViewController {
|
||||
|
||||
func loadContent(refreshing: Bool) {}
|
||||
|
||||
func loadMoreContent() {}
|
||||
|
||||
func beginLoading(refreshing: Bool) {
|
||||
if !refreshing { beginNavigationLoading(spinner) }
|
||||
}
|
||||
@@ -55,6 +63,58 @@ class RefreshingTableViewController: UITableViewController {
|
||||
refreshControl?.endRefreshing()
|
||||
}
|
||||
|
||||
func resetPagination() {
|
||||
hasMoreContent = false
|
||||
loadingMore = false
|
||||
tableView.tableFooterView = nil
|
||||
}
|
||||
|
||||
func finishPagination(hasMore: Bool) {
|
||||
hasMoreContent = hasMore
|
||||
loadingMore = false
|
||||
var configuration = moreButton.configuration
|
||||
configuration?.title = "Pull up or tap to load more"
|
||||
configuration?.showsActivityIndicator = false
|
||||
moreButton.configuration = configuration
|
||||
moreButton.accessibilityLabel = "Load more results"
|
||||
tableView.tableFooterView = hasMore ? paginationFooter() : nil
|
||||
}
|
||||
|
||||
func failPagination() {
|
||||
loadingMore = false
|
||||
finishPagination(hasMore: hasMoreContent)
|
||||
}
|
||||
|
||||
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard scrollView.isDragging, hasMoreContent, !loadingMore else { return }
|
||||
let bottom = max(
|
||||
-scrollView.adjustedContentInset.top,
|
||||
scrollView.contentSize.height
|
||||
+ scrollView.adjustedContentInset.bottom
|
||||
- scrollView.bounds.height
|
||||
)
|
||||
if scrollView.contentOffset.y > bottom + 60 { requestMoreContent() }
|
||||
}
|
||||
|
||||
private func requestMoreContent() {
|
||||
guard hasMoreContent, !loadingMore else { return }
|
||||
loadingMore = true
|
||||
var configuration = moreButton.configuration
|
||||
configuration?.title = "Loading more…"
|
||||
configuration?.showsActivityIndicator = true
|
||||
moreButton.configuration = configuration
|
||||
moreButton.accessibilityLabel = "Loading more results"
|
||||
loadMoreContent()
|
||||
}
|
||||
|
||||
private func paginationFooter() -> UIView {
|
||||
let footer = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 56))
|
||||
moreButton.frame = footer.bounds
|
||||
moreButton.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
footer.addSubview(moreButton)
|
||||
return footer
|
||||
}
|
||||
|
||||
@objc private func refreshRequested() {
|
||||
loadContent(refreshing: true)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user