Add paginated panel loading

This commit is contained in:
Georg Bauer
2026-07-31 15:02:21 +02:00
parent 228879da15
commit 33f9eb809c
11 changed files with 1104 additions and 290 deletions

View File

@@ -613,11 +613,11 @@ public protocol GotchaCoreProtocol: AnyObject, Sendable {
func commitFiles(owner: String, repository: String, sha: String) async throws -> [FileRow]
func commits(owner: String, repository: String, branch: String?) async throws -> CommitPage
func commits(owner: String, repository: String, branch: String?, pages: UInt32) async throws -> CommitPage
func home() async throws -> HomePage
func home(page: UInt32) async throws -> HomePage
func issue(owner: String, repository: String, number: Int64) async throws -> IssuePage
func issue(owner: String, repository: String, number: Int64, page: UInt32) async throws -> IssuePage
func issueEditor(owner: String, repository: String, number: Int64?) async throws -> IssueEditorPage
@@ -625,13 +625,13 @@ public protocol GotchaCoreProtocol: AnyObject, Sendable {
func issueFiltersActive(owner: String, repository: String) throws -> Bool
func issues(owner: String, repository: String) async throws -> [IssueRow]
func issues(owner: String, repository: String, page: UInt32) async throws -> IssueListPage
func milestone(owner: String, repository: String, id: Int64) async throws -> MilestonePage
func milestone(owner: String, repository: String, id: Int64, page: UInt32) async throws -> MilestonePage
func milestones(owner: String, repository: String) async throws -> [MilestoneRow]
func milestones(owner: String, repository: String, page: UInt32) async throws -> MilestoneListPage
func pull(owner: String, repository: String, number: Int64) async throws -> PullPage
func pull(owner: String, repository: String, number: Int64, page: UInt32) async throws -> PullPage
func pullDiff(owner: String, repository: String, number: Int64, path: String) async throws -> DiffPage
@@ -639,9 +639,9 @@ public protocol GotchaCoreProtocol: AnyObject, Sendable {
func pullFiltersActive() throws -> Bool
func pulls() async throws -> [PullRow]
func pulls(page: UInt32) async throws -> PullListPage
func repositories() async throws -> [RepositoryRow]
func repositories(page: UInt32) async throws -> RepositoryListPage
func repositoryContents(owner: String, repository: String, path: String) async throws -> [RepositoryContentRow]
@@ -797,12 +797,12 @@ open func commitFiles(owner: String, repository: String, sha: String)async throw
)
}
open func commits(owner: String, repository: String, branch: String?)async throws -> CommitPage {
open func commits(owner: String, repository: String, branch: String?, pages: UInt32)async throws -> CommitPage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_commits(
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterOptionString.lower(branch)
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterOptionString.lower(branch),FfiConverterUInt32.lower(pages)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
@@ -813,12 +813,12 @@ open func commits(owner: String, repository: String, branch: String?)async throw
)
}
open func home()async throws -> HomePage {
open func home(page: UInt32)async throws -> HomePage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_home(
self.uniffiCloneHandle()
self.uniffiCloneHandle(),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
@@ -829,12 +829,12 @@ open func home()async throws -> HomePage {
)
}
open func issue(owner: String, repository: String, number: Int64)async throws -> IssuePage {
open func issue(owner: String, repository: String, number: Int64, page: UInt32)async throws -> IssuePage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_issue(
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(number)
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(number),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
@@ -888,28 +888,28 @@ open func issueFiltersActive(owner: String, repository: String)throws -> Bool
})
}
open func issues(owner: String, repository: String)async throws -> [IssueRow] {
open func issues(owner: String, repository: String, page: UInt32)async throws -> IssueListPage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_issues(
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository)
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeIssueRow.lift,
liftFunc: FfiConverterTypeIssueListPage_lift,
errorHandler: FfiConverterTypeGotchaError_lift
)
}
open func milestone(owner: String, repository: String, id: Int64)async throws -> MilestonePage {
open func milestone(owner: String, repository: String, id: Int64, page: UInt32)async throws -> MilestonePage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_milestone(
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(id)
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(id),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
@@ -920,28 +920,28 @@ open func milestone(owner: String, repository: String, id: Int64)async throws -
)
}
open func milestones(owner: String, repository: String)async throws -> [MilestoneRow] {
open func milestones(owner: String, repository: String, page: UInt32)async throws -> MilestoneListPage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_milestones(
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository)
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeMilestoneRow.lift,
liftFunc: FfiConverterTypeMilestoneListPage_lift,
errorHandler: FfiConverterTypeGotchaError_lift
)
}
open func pull(owner: String, repository: String, number: Int64)async throws -> PullPage {
open func pull(owner: String, repository: String, number: Int64, page: UInt32)async throws -> PullPage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_pull(
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(number)
self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(number),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
@@ -993,34 +993,34 @@ open func pullFiltersActive()throws -> Bool {
})
}
open func pulls()async throws -> [PullRow] {
open func pulls(page: UInt32)async throws -> PullListPage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_pulls(
self.uniffiCloneHandle()
self.uniffiCloneHandle(),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypePullRow.lift,
liftFunc: FfiConverterTypePullListPage_lift,
errorHandler: FfiConverterTypeGotchaError_lift
)
}
open func repositories()async throws -> [RepositoryRow] {
open func repositories(page: UInt32)async throws -> RepositoryListPage {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_gotcha_core_fn_method_gotchacore_repositories(
self.uniffiCloneHandle()
self.uniffiCloneHandle(),FfiConverterUInt32.lower(page)
)
},
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
liftFunc: FfiConverterSequenceTypeRepositoryRow.lift,
liftFunc: FfiConverterTypeRepositoryListPage_lift,
errorHandler: FfiConverterTypeGotchaError_lift
)
}
@@ -1360,13 +1360,15 @@ public struct CommitPage: Equatable, Hashable {
public var branches: [String]
public var commits: [CommitRow]
public var laneCount: UInt32
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(branches: [String], commits: [CommitRow], laneCount: UInt32) {
public init(branches: [String], commits: [CommitRow], laneCount: UInt32, hasMore: Bool) {
self.branches = branches
self.commits = commits
self.laneCount = laneCount
self.hasMore = hasMore
}
@@ -1387,7 +1389,8 @@ public struct FfiConverterTypeCommitPage: FfiConverterRustBuffer {
try CommitPage(
branches: FfiConverterSequenceString.read(from: &buf),
commits: FfiConverterSequenceTypeCommitRow.read(from: &buf),
laneCount: FfiConverterUInt32.read(from: &buf)
laneCount: FfiConverterUInt32.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
@@ -1395,6 +1398,7 @@ public struct FfiConverterTypeCommitPage: FfiConverterRustBuffer {
FfiConverterSequenceString.write(value.branches, into: &buf)
FfiConverterSequenceTypeCommitRow.write(value.commits, into: &buf)
FfiConverterUInt32.write(value.laneCount, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
@@ -1608,6 +1612,60 @@ public func FfiConverterTypeDiffPage_lower(_ value: DiffPage) -> RustBuffer {
}
public struct FileListPage: Equatable, Hashable {
public var rows: [FileRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(rows: [FileRow], hasMore: Bool) {
self.rows = rows
self.hasMore = hasMore
}
}
#if compiler(>=6)
extension FileListPage: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeFileListPage: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> FileListPage {
return
try FileListPage(
rows: FfiConverterSequenceTypeFileRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
public static func write(_ value: FileListPage, into buf: inout [UInt8]) {
FfiConverterSequenceTypeFileRow.write(value.rows, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeFileListPage_lift(_ buf: RustBuffer) throws -> FileListPage {
return try FfiConverterTypeFileListPage.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeFileListPage_lower(_ value: FileListPage) -> RustBuffer {
return FfiConverterTypeFileListPage.lower(value)
}
public struct FileRow: Equatable, Hashable {
public var path: String
public var status: String
@@ -1723,16 +1781,18 @@ public struct HomePage: Equatable, Hashable {
public var pullActivities: [ActivityRow]
public var heatCells: [HeatCell]
public var contributionCount: Int64
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(serverName: String, activities: [ActivityRow], issueActivities: [ActivityRow], pullActivities: [ActivityRow], heatCells: [HeatCell], contributionCount: Int64) {
public init(serverName: String, activities: [ActivityRow], issueActivities: [ActivityRow], pullActivities: [ActivityRow], heatCells: [HeatCell], contributionCount: Int64, hasMore: Bool) {
self.serverName = serverName
self.activities = activities
self.issueActivities = issueActivities
self.pullActivities = pullActivities
self.heatCells = heatCells
self.contributionCount = contributionCount
self.hasMore = hasMore
}
@@ -1756,7 +1816,8 @@ public struct FfiConverterTypeHomePage: FfiConverterRustBuffer {
issueActivities: FfiConverterSequenceTypeActivityRow.read(from: &buf),
pullActivities: FfiConverterSequenceTypeActivityRow.read(from: &buf),
heatCells: FfiConverterSequenceTypeHeatCell.read(from: &buf),
contributionCount: FfiConverterInt64.read(from: &buf)
contributionCount: FfiConverterInt64.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
@@ -1767,6 +1828,7 @@ public struct FfiConverterTypeHomePage: FfiConverterRustBuffer {
FfiConverterSequenceTypeActivityRow.write(value.pullActivities, into: &buf)
FfiConverterSequenceTypeHeatCell.write(value.heatCells, into: &buf)
FfiConverterInt64.write(value.contributionCount, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
@@ -2034,6 +2096,60 @@ public func FfiConverterTypeIssueFilterOptions_lower(_ value: IssueFilterOptions
}
public struct IssueListPage: Equatable, Hashable {
public var rows: [IssueRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(rows: [IssueRow], hasMore: Bool) {
self.rows = rows
self.hasMore = hasMore
}
}
#if compiler(>=6)
extension IssueListPage: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeIssueListPage: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> IssueListPage {
return
try IssueListPage(
rows: FfiConverterSequenceTypeIssueRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
public static func write(_ value: IssueListPage, into buf: inout [UInt8]) {
FfiConverterSequenceTypeIssueRow.write(value.rows, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeIssueListPage_lift(_ buf: RustBuffer) throws -> IssueListPage {
return try FfiConverterTypeIssueListPage.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeIssueListPage_lower(_ value: IssueListPage) -> RustBuffer {
return FfiConverterTypeIssueListPage.lower(value)
}
public struct IssuePage: Equatable, Hashable {
public var title: String
public var state: String
@@ -2041,16 +2157,18 @@ public struct IssuePage: Equatable, Hashable {
public var milestone: String
public var body: String
public var comments: [CommentRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(title: String, state: String, meta: String, milestone: String, body: String, comments: [CommentRow]) {
public init(title: String, state: String, meta: String, milestone: String, body: String, comments: [CommentRow], hasMore: Bool) {
self.title = title
self.state = state
self.meta = meta
self.milestone = milestone
self.body = body
self.comments = comments
self.hasMore = hasMore
}
@@ -2074,7 +2192,8 @@ public struct FfiConverterTypeIssuePage: FfiConverterRustBuffer {
meta: FfiConverterString.read(from: &buf),
milestone: FfiConverterString.read(from: &buf),
body: FfiConverterString.read(from: &buf),
comments: FfiConverterSequenceTypeCommentRow.read(from: &buf)
comments: FfiConverterSequenceTypeCommentRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
@@ -2085,6 +2204,7 @@ public struct FfiConverterTypeIssuePage: FfiConverterRustBuffer {
FfiConverterString.write(value.milestone, into: &buf)
FfiConverterString.write(value.body, into: &buf)
FfiConverterSequenceTypeCommentRow.write(value.comments, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
@@ -2232,17 +2352,73 @@ public func FfiConverterTypeLabelRow_lower(_ value: LabelRow) -> RustBuffer {
}
public struct MilestoneListPage: Equatable, Hashable {
public var rows: [MilestoneRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(rows: [MilestoneRow], hasMore: Bool) {
self.rows = rows
self.hasMore = hasMore
}
}
#if compiler(>=6)
extension MilestoneListPage: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMilestoneListPage: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MilestoneListPage {
return
try MilestoneListPage(
rows: FfiConverterSequenceTypeMilestoneRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
public static func write(_ value: MilestoneListPage, into buf: inout [UInt8]) {
FfiConverterSequenceTypeMilestoneRow.write(value.rows, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMilestoneListPage_lift(_ buf: RustBuffer) throws -> MilestoneListPage {
return try FfiConverterTypeMilestoneListPage.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMilestoneListPage_lower(_ value: MilestoneListPage) -> RustBuffer {
return FfiConverterTypeMilestoneListPage.lower(value)
}
public struct MilestonePage: Equatable, Hashable {
public var milestone: MilestoneRow
public var issues: [IssueRow]
public var pulls: [PullRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(milestone: MilestoneRow, issues: [IssueRow], pulls: [PullRow]) {
public init(milestone: MilestoneRow, issues: [IssueRow], pulls: [PullRow], hasMore: Bool) {
self.milestone = milestone
self.issues = issues
self.pulls = pulls
self.hasMore = hasMore
}
@@ -2263,7 +2439,8 @@ public struct FfiConverterTypeMilestonePage: FfiConverterRustBuffer {
try MilestonePage(
milestone: FfiConverterTypeMilestoneRow.read(from: &buf),
issues: FfiConverterSequenceTypeIssueRow.read(from: &buf),
pulls: FfiConverterSequenceTypePullRow.read(from: &buf)
pulls: FfiConverterSequenceTypePullRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
@@ -2271,6 +2448,7 @@ public struct FfiConverterTypeMilestonePage: FfiConverterRustBuffer {
FfiConverterTypeMilestoneRow.write(value.milestone, into: &buf)
FfiConverterSequenceTypeIssueRow.write(value.issues, into: &buf)
FfiConverterSequenceTypePullRow.write(value.pulls, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
@@ -2418,6 +2596,60 @@ public func FfiConverterTypePullFilterOptions_lower(_ value: PullFilterOptions)
}
public struct PullListPage: Equatable, Hashable {
public var rows: [PullRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(rows: [PullRow], hasMore: Bool) {
self.rows = rows
self.hasMore = hasMore
}
}
#if compiler(>=6)
extension PullListPage: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypePullListPage: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PullListPage {
return
try PullListPage(
rows: FfiConverterSequenceTypePullRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
public static func write(_ value: PullListPage, into buf: inout [UInt8]) {
FfiConverterSequenceTypePullRow.write(value.rows, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypePullListPage_lift(_ buf: RustBuffer) throws -> PullListPage {
return try FfiConverterTypePullListPage.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypePullListPage_lower(_ value: PullListPage) -> RustBuffer {
return FfiConverterTypePullListPage.lower(value)
}
public struct PullPage: Equatable, Hashable {
public var title: String
public var meta: String
@@ -2425,16 +2657,18 @@ public struct PullPage: Equatable, Hashable {
public var filesRef: String
public var files: [FileRow]
public var comments: [CommentRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(title: String, meta: String, body: String, filesRef: String, files: [FileRow], comments: [CommentRow]) {
public init(title: String, meta: String, body: String, filesRef: String, files: [FileRow], comments: [CommentRow], hasMore: Bool) {
self.title = title
self.meta = meta
self.body = body
self.filesRef = filesRef
self.files = files
self.comments = comments
self.hasMore = hasMore
}
@@ -2458,7 +2692,8 @@ public struct FfiConverterTypePullPage: FfiConverterRustBuffer {
body: FfiConverterString.read(from: &buf),
filesRef: FfiConverterString.read(from: &buf),
files: FfiConverterSequenceTypeFileRow.read(from: &buf),
comments: FfiConverterSequenceTypeCommentRow.read(from: &buf)
comments: FfiConverterSequenceTypeCommentRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
@@ -2469,6 +2704,7 @@ public struct FfiConverterTypePullPage: FfiConverterRustBuffer {
FfiConverterString.write(value.filesRef, into: &buf)
FfiConverterSequenceTypeFileRow.write(value.files, into: &buf)
FfiConverterSequenceTypeCommentRow.write(value.comments, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
@@ -2686,6 +2922,60 @@ public func FfiConverterTypeRepositoryFilePage_lower(_ value: RepositoryFilePage
}
public struct RepositoryListPage: Equatable, Hashable {
public var rows: [RepositoryRow]
public var hasMore: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(rows: [RepositoryRow], hasMore: Bool) {
self.rows = rows
self.hasMore = hasMore
}
}
#if compiler(>=6)
extension RepositoryListPage: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeRepositoryListPage: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RepositoryListPage {
return
try RepositoryListPage(
rows: FfiConverterSequenceTypeRepositoryRow.read(from: &buf),
hasMore: FfiConverterBool.read(from: &buf)
)
}
public static func write(_ value: RepositoryListPage, into buf: inout [UInt8]) {
FfiConverterSequenceTypeRepositoryRow.write(value.rows, into: &buf)
FfiConverterBool.write(value.hasMore, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeRepositoryListPage_lift(_ buf: RustBuffer) throws -> RepositoryListPage {
return try FfiConverterTypeRepositoryListPage.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeRepositoryListPage_lower(_ value: RepositoryListPage) -> RustBuffer {
return FfiConverterTypeRepositoryListPage.lower(value)
}
public struct RepositoryRow: Equatable, Hashable {
public var name: String
public var owner: String
@@ -3538,13 +3828,13 @@ private let initializationResult: InitializationResult = {
if (uniffi_gotcha_core_checksum_method_gotchacore_commit_files() != 43926) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_commits() != 472) {
if (uniffi_gotcha_core_checksum_method_gotchacore_commits() != 59625) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_home() != 5983) {
if (uniffi_gotcha_core_checksum_method_gotchacore_home() != 38990) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_issue() != 56735) {
if (uniffi_gotcha_core_checksum_method_gotchacore_issue() != 3108) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_issue_editor() != 10078) {
@@ -3556,16 +3846,16 @@ private let initializationResult: InitializationResult = {
if (uniffi_gotcha_core_checksum_method_gotchacore_issue_filters_active() != 51470) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_issues() != 1316) {
if (uniffi_gotcha_core_checksum_method_gotchacore_issues() != 8566) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_milestone() != 26979) {
if (uniffi_gotcha_core_checksum_method_gotchacore_milestone() != 57186) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_milestones() != 27282) {
if (uniffi_gotcha_core_checksum_method_gotchacore_milestones() != 50431) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_pull() != 46939) {
if (uniffi_gotcha_core_checksum_method_gotchacore_pull() != 12919) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_pull_diff() != 61384) {
@@ -3577,10 +3867,10 @@ private let initializationResult: InitializationResult = {
if (uniffi_gotcha_core_checksum_method_gotchacore_pull_filters_active() != 20260) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_pulls() != 13092) {
if (uniffi_gotcha_core_checksum_method_gotchacore_pulls() != 37027) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_repositories() != 12256) {
if (uniffi_gotcha_core_checksum_method_gotchacore_repositories() != 31164) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_gotcha_core_checksum_method_gotchacore_repository_contents() != 8454) {

View File

@@ -286,17 +286,17 @@ uint64_t uniffi_gotcha_core_fn_method_gotchacore_commit_files(uint64_t ptr, Rust
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMITS
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMITS
uint64_t uniffi_gotcha_core_fn_method_gotchacore_commits(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer branch
uint64_t uniffi_gotcha_core_fn_method_gotchacore_commits(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer branch, uint32_t pages
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_HOME
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_HOME
uint64_t uniffi_gotcha_core_fn_method_gotchacore_home(uint64_t ptr
uint64_t uniffi_gotcha_core_fn_method_gotchacore_home(uint64_t ptr, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE
uint64_t uniffi_gotcha_core_fn_method_gotchacore_issue(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t number
uint64_t uniffi_gotcha_core_fn_method_gotchacore_issue(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t number, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE_EDITOR
@@ -316,22 +316,22 @@ int8_t uniffi_gotcha_core_fn_method_gotchacore_issue_filters_active(uint64_t ptr
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUES
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUES
uint64_t uniffi_gotcha_core_fn_method_gotchacore_issues(uint64_t ptr, RustBuffer owner, RustBuffer repository
uint64_t uniffi_gotcha_core_fn_method_gotchacore_issues(uint64_t ptr, RustBuffer owner, RustBuffer repository, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONE
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONE
uint64_t uniffi_gotcha_core_fn_method_gotchacore_milestone(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t id
uint64_t uniffi_gotcha_core_fn_method_gotchacore_milestone(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t id, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONES
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONES
uint64_t uniffi_gotcha_core_fn_method_gotchacore_milestones(uint64_t ptr, RustBuffer owner, RustBuffer repository
uint64_t uniffi_gotcha_core_fn_method_gotchacore_milestones(uint64_t ptr, RustBuffer owner, RustBuffer repository, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL
uint64_t uniffi_gotcha_core_fn_method_gotchacore_pull(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t number
uint64_t uniffi_gotcha_core_fn_method_gotchacore_pull(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t number, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL_DIFF
@@ -351,12 +351,12 @@ int8_t uniffi_gotcha_core_fn_method_gotchacore_pull_filters_active(uint64_t ptr,
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULLS
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULLS
uint64_t uniffi_gotcha_core_fn_method_gotchacore_pulls(uint64_t ptr
uint64_t uniffi_gotcha_core_fn_method_gotchacore_pulls(uint64_t ptr, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORIES
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORIES
uint64_t uniffi_gotcha_core_fn_method_gotchacore_repositories(uint64_t ptr
uint64_t uniffi_gotcha_core_fn_method_gotchacore_repositories(uint64_t ptr, uint32_t page
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORY_CONTENTS

View File

@@ -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() }
}
}

View File

@@ -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() }
}
}
}

View File

@@ -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() }
}
}

View File

@@ -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)
}