Add paginated panel loading
This commit is contained in:
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user