Add paginated panel loading
This commit is contained in:
14
TESTING.md
14
TESTING.md
@@ -99,6 +99,20 @@ xcrun simctl launch booted de.rfc1437.gotcha
|
|||||||
- [ ] Pull past the top of Home, repository lists, issue lists, pull lists,
|
- [ ] Pull past the top of Home, repository lists, issue lists, pull lists,
|
||||||
commit history, changed files, issue details, pull details, and diffs.
|
commit history, changed files, issue details, pull details, and diffs.
|
||||||
The native refresh control appears, reloads data, and disappears.
|
The native refresh control appears, reloads data, and disappears.
|
||||||
|
- [ ] On accounts/repositories with more than 30 results, Home activity,
|
||||||
|
repositories, issues, pull requests, milestones, milestone contents,
|
||||||
|
commit history, and pull-request files initially show one page plus a
|
||||||
|
**Pull up or tap to load more**
|
||||||
|
footer. Pull upward past the bottom and verify the footer changes to a
|
||||||
|
native loading indicator, the next page appends without duplicates, and
|
||||||
|
the footer disappears after the final page.
|
||||||
|
- [ ] Tap the load-more footer as an accessibility alternative and repeat while
|
||||||
|
VoiceOver is running. It announces loading, ignores repeated activation
|
||||||
|
while a request is active, preserves the current scroll position, and
|
||||||
|
allows retry after an error.
|
||||||
|
- [ ] Pull down to refresh after loading multiple pages. The content resets to
|
||||||
|
the new first page, filtering/branch changes also reset pagination, and
|
||||||
|
switching History/Files never shows a stale load-more footer.
|
||||||
- [ ] Tap a row, scroll its detail, and use both the navigation-bar Back button
|
- [ ] Tap a row, scroll its detail, and use both the navigation-bar Back button
|
||||||
and the left-edge interactive swipe to return.
|
and the left-edge interactive swipe to return.
|
||||||
- [ ] From a list scrolled well away from the top, open a detail and go Back.
|
- [ ] From a list scrolled well away from the top, open a detail and go Back.
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ use crate::{
|
|||||||
diff,
|
diff,
|
||||||
domain::{
|
domain::{
|
||||||
HistoryCommit, HomeData, IssueDetails, IssueDraft, IssueEditorData, MilestoneDetails,
|
HistoryCommit, HomeData, IssueDetails, IssueDraft, IssueEditorData, MilestoneDetails,
|
||||||
PullDetails, RepositoryData, Server, api_date,
|
PAGE_SIZE, Page, PullDetails, RepositoryData, Server, api_date,
|
||||||
},
|
},
|
||||||
presentation::compact_date,
|
presentation::compact_date,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn load_repositories(server: &Server) -> Result<Vec<RepositoryData>, String> {
|
pub async fn load_repositories(server: &Server, page: i32) -> Result<Page<RepositoryData>, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
let configuration = client.configuration();
|
let configuration = client.configuration();
|
||||||
@@ -24,36 +24,41 @@ pub async fn load_repositories(server: &Server) -> Result<Vec<RepositoryData>, S
|
|||||||
.await
|
.await
|
||||||
.map_err(|error| error.to_string())?;
|
.map_err(|error| error.to_string())?;
|
||||||
let login = user.login.unwrap_or_default();
|
let login = user.login.unwrap_or_default();
|
||||||
let repositories = apis::user_api::user_current_list_repos(&configuration, Some(1), Some(100))
|
let repositories =
|
||||||
.await
|
apis::user_api::user_current_list_repos(&configuration, Some(page), Some(PAGE_SIZE))
|
||||||
.map_err(|error| error.to_string())?;
|
.await
|
||||||
Ok(repositories
|
.map_err(|error| error.to_string())?;
|
||||||
.into_iter()
|
let has_more = repositories.len() == PAGE_SIZE as usize;
|
||||||
.filter(|repository| {
|
Ok(Page {
|
||||||
repository
|
items: repositories
|
||||||
.owner
|
.into_iter()
|
||||||
.as_ref()
|
.filter(|repository| {
|
||||||
.and_then(|owner| owner.login.as_deref())
|
repository
|
||||||
== Some(login.as_str())
|
.owner
|
||||||
})
|
.as_ref()
|
||||||
.filter_map(|repository| {
|
.and_then(|owner| owner.login.as_deref())
|
||||||
Some(RepositoryData {
|
== Some(login.as_str())
|
||||||
owner: repository.owner?.login?,
|
|
||||||
name: repository.name?,
|
|
||||||
description: repository
|
|
||||||
.description
|
|
||||||
.filter(|text| !text.is_empty())
|
|
||||||
.unwrap_or_else(|| "No description".into()),
|
|
||||||
language: repository
|
|
||||||
.language
|
|
||||||
.filter(|text| !text.is_empty())
|
|
||||||
.unwrap_or_else(|| "Unknown language".into()),
|
|
||||||
open_issues: repository.open_issues_count.unwrap_or_default(),
|
|
||||||
updated: compact_date(repository.updated_at.as_deref()),
|
|
||||||
default_branch: repository.default_branch.unwrap_or_else(|| "main".into()),
|
|
||||||
})
|
})
|
||||||
})
|
.filter_map(|repository| {
|
||||||
.collect())
|
Some(RepositoryData {
|
||||||
|
owner: repository.owner?.login?,
|
||||||
|
name: repository.name?,
|
||||||
|
description: repository
|
||||||
|
.description
|
||||||
|
.filter(|text| !text.is_empty())
|
||||||
|
.unwrap_or_else(|| "No description".into()),
|
||||||
|
language: repository
|
||||||
|
.language
|
||||||
|
.filter(|text| !text.is_empty())
|
||||||
|
.unwrap_or_else(|| "Unknown language".into()),
|
||||||
|
open_issues: repository.open_issues_count.unwrap_or_default(),
|
||||||
|
updated: compact_date(repository.updated_at.as_deref()),
|
||||||
|
default_branch: repository.default_branch.unwrap_or_else(|| "main".into()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
has_more,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_issues(
|
pub async fn load_issues(
|
||||||
@@ -63,7 +68,8 @@ pub async fn load_issues(
|
|||||||
status: &str,
|
status: &str,
|
||||||
labels: &[String],
|
labels: &[String],
|
||||||
milestone: &str,
|
milestone: &str,
|
||||||
) -> Result<Vec<models::Issue>, String> {
|
page: i32,
|
||||||
|
) -> Result<Page<models::Issue>, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
let labels = (!labels.is_empty()).then(|| labels.join(","));
|
let labels = (!labels.is_empty()).then(|| labels.join(","));
|
||||||
@@ -81,10 +87,33 @@ pub async fn load_issues(
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Some(1),
|
Some(page),
|
||||||
Some(100),
|
Some(PAGE_SIZE),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map(Page::from_items)
|
||||||
|
.map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_milestones_page(
|
||||||
|
server: &Server,
|
||||||
|
owner: &str,
|
||||||
|
repository: &str,
|
||||||
|
page: i32,
|
||||||
|
) -> Result<Page<models::Milestone>, String> {
|
||||||
|
let client =
|
||||||
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
|
apis::issue_api::issue_get_milestones_list(
|
||||||
|
&client.configuration(),
|
||||||
|
owner,
|
||||||
|
repository,
|
||||||
|
Some("all"),
|
||||||
|
None,
|
||||||
|
Some(page),
|
||||||
|
Some(PAGE_SIZE),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(Page::from_items)
|
||||||
.map_err(|error| error.to_string())
|
.map_err(|error| error.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +180,7 @@ pub async fn load_milestone(
|
|||||||
owner: &str,
|
owner: &str,
|
||||||
repository: &str,
|
repository: &str,
|
||||||
id: i64,
|
id: i64,
|
||||||
|
page: i32,
|
||||||
) -> Result<MilestoneDetails, String> {
|
) -> Result<MilestoneDetails, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
@@ -174,12 +204,14 @@ pub async fn load_milestone(
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Some(1),
|
Some(page),
|
||||||
Some(100),
|
Some(PAGE_SIZE),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let (issues, pulls) = tokio::join!(load("issues"), load("pulls"));
|
let (issues, pulls) = tokio::join!(load("issues"), load("pulls"));
|
||||||
|
let issues = issues.map_err(|error| error.to_string())?;
|
||||||
let mut pulls = pulls.map_err(|error| error.to_string())?;
|
let mut pulls = pulls.map_err(|error| error.to_string())?;
|
||||||
|
let has_more = issues.len() == PAGE_SIZE as usize || pulls.len() == PAGE_SIZE as usize;
|
||||||
for pull in &mut pulls {
|
for pull in &mut pulls {
|
||||||
pull.repository.get_or_insert_with(|| {
|
pull.repository.get_or_insert_with(|| {
|
||||||
Box::new(models::RepositoryMeta {
|
Box::new(models::RepositoryMeta {
|
||||||
@@ -191,8 +223,9 @@ pub async fn load_milestone(
|
|||||||
}
|
}
|
||||||
Ok(MilestoneDetails {
|
Ok(MilestoneDetails {
|
||||||
milestone,
|
milestone,
|
||||||
issues: issues.map_err(|error| error.to_string())?,
|
issues,
|
||||||
pulls,
|
pulls,
|
||||||
|
has_more,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +233,8 @@ pub async fn load_pulls(
|
|||||||
server: &Server,
|
server: &Server,
|
||||||
status: &str,
|
status: &str,
|
||||||
milestone: &str,
|
milestone: &str,
|
||||||
) -> Result<Vec<models::Issue>, String> {
|
page: i32,
|
||||||
|
) -> Result<Page<models::Issue>, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
let configuration = client.configuration();
|
let configuration = client.configuration();
|
||||||
@@ -210,42 +244,35 @@ pub async fn load_pulls(
|
|||||||
.map_err(|error| error.to_string())?
|
.map_err(|error| error.to_string())?
|
||||||
.login
|
.login
|
||||||
.ok_or("The server account has no username.")?;
|
.ok_or("The server account has no username.")?;
|
||||||
let mut pulls = Vec::new();
|
apis::issue_api::issue_search_issues(
|
||||||
for page in 1.. {
|
&configuration,
|
||||||
let batch = apis::issue_api::issue_search_issues(
|
Some(status),
|
||||||
&configuration,
|
None,
|
||||||
Some(status),
|
(!milestone.is_empty()).then_some(milestone),
|
||||||
None,
|
None,
|
||||||
(!milestone.is_empty()).then_some(milestone),
|
None,
|
||||||
None,
|
Some("pulls"),
|
||||||
None,
|
None,
|
||||||
Some("pulls"),
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
Some(&owner),
|
||||||
None,
|
None,
|
||||||
Some(&owner),
|
Some(page),
|
||||||
None,
|
Some(PAGE_SIZE),
|
||||||
Some(page),
|
)
|
||||||
Some(100),
|
.await
|
||||||
)
|
.map(Page::from_items)
|
||||||
.await
|
.map_err(|error| error.to_string())
|
||||||
.map_err(|error| error.to_string())?;
|
|
||||||
if batch.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pulls.extend(batch);
|
|
||||||
}
|
|
||||||
Ok(pulls)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_pull_milestones(server: &Server) -> Result<Vec<String>, String> {
|
pub async fn load_pull_milestones(server: &Server) -> Result<Vec<String>, String> {
|
||||||
let (open, closed) = tokio::try_join!(
|
let (open, closed) = tokio::try_join!(
|
||||||
load_pulls(server, "open", ""),
|
load_all_pulls(server, "open"),
|
||||||
load_pulls(server, "closed", "")
|
load_all_pulls(server, "closed")
|
||||||
)?;
|
)?;
|
||||||
Ok(open
|
Ok(open
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -256,6 +283,18 @@ pub async fn load_pull_milestones(server: &Server) -> Result<Vec<String>, String
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_all_pulls(server: &Server, status: &str) -> Result<Vec<models::Issue>, String> {
|
||||||
|
let mut pulls = Vec::new();
|
||||||
|
for page in 1.. {
|
||||||
|
let batch = load_pulls(server, status, "", page).await?;
|
||||||
|
pulls.extend(batch.items);
|
||||||
|
if !batch.has_more {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(pulls)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn load_branches(
|
pub async fn load_branches(
|
||||||
server: &Server,
|
server: &Server,
|
||||||
owner: &str,
|
owner: &str,
|
||||||
@@ -336,11 +375,14 @@ pub async fn load_branch_commits(
|
|||||||
owner: &str,
|
owner: &str,
|
||||||
repository: &str,
|
repository: &str,
|
||||||
branch: &str,
|
branch: &str,
|
||||||
) -> Result<Vec<HistoryCommit>, String> {
|
pages: u32,
|
||||||
load_commits_for_ref(server, owner, repository, Some(branch))
|
) -> Result<Page<HistoryCommit>, String> {
|
||||||
|
load_commits_for_ref(server, owner, repository, Some(branch), pages)
|
||||||
.await
|
.await
|
||||||
.map(|commits| {
|
.map(|page| Page {
|
||||||
commits
|
has_more: page.has_more,
|
||||||
|
items: page
|
||||||
|
.items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|commit| HistoryCommit {
|
.map(|commit| HistoryCommit {
|
||||||
commit,
|
commit,
|
||||||
@@ -350,7 +392,7 @@ pub async fn load_branch_commits(
|
|||||||
connections: Vec::new(),
|
connections: Vec::new(),
|
||||||
refs: Vec::new(),
|
refs: Vec::new(),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,13 +401,15 @@ pub async fn load_all_commits(
|
|||||||
owner: &str,
|
owner: &str,
|
||||||
repository: &str,
|
repository: &str,
|
||||||
branches: &[String],
|
branches: &[String],
|
||||||
) -> Result<Vec<HistoryCommit>, String> {
|
pages: u32,
|
||||||
|
) -> Result<Page<HistoryCommit>, String> {
|
||||||
let mut tasks = JoinSet::new();
|
let mut tasks = JoinSet::new();
|
||||||
for (lane, branch) in branches.iter().cloned().enumerate() {
|
for (lane, branch) in branches.iter().cloned().enumerate() {
|
||||||
let (server, owner, repository) =
|
let (server, owner, repository) =
|
||||||
(server.clone(), owner.to_string(), repository.to_string());
|
(server.clone(), owner.to_string(), repository.to_string());
|
||||||
tasks.spawn(async move {
|
tasks.spawn(async move {
|
||||||
let commits = load_commits_for_ref(&server, &owner, &repository, Some(&branch)).await?;
|
let commits =
|
||||||
|
load_commits_for_ref(&server, &owner, &repository, Some(&branch), pages).await?;
|
||||||
Ok::<_, String>((lane, branch, commits))
|
Ok::<_, String>((lane, branch, commits))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -375,11 +419,19 @@ pub async fn load_all_commits(
|
|||||||
histories.push(result.map_err(|error| error.to_string())??);
|
histories.push(result.map_err(|error| error.to_string())??);
|
||||||
}
|
}
|
||||||
histories.sort_by_key(|(lane, _, _)| *lane);
|
histories.sort_by_key(|(lane, _, _)| *lane);
|
||||||
|
let has_more = histories.iter().any(|(_, _, page)| page.has_more);
|
||||||
|
let histories = histories
|
||||||
|
.into_iter()
|
||||||
|
.map(|(lane, branch, page)| (lane, branch, page.items))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let pull_refs = load_pull_refs(server, owner, repository)
|
let pull_refs = load_pull_refs(server, owner, repository)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Ok(build_graph(histories, pull_refs))
|
Ok(Page {
|
||||||
|
items: build_graph(histories, pull_refs),
|
||||||
|
has_more,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_pull_files(
|
pub async fn load_pull_files(
|
||||||
@@ -387,28 +439,21 @@ pub async fn load_pull_files(
|
|||||||
owner: &str,
|
owner: &str,
|
||||||
repository: &str,
|
repository: &str,
|
||||||
number: i64,
|
number: i64,
|
||||||
) -> Result<Vec<models::ChangedFile>, String> {
|
page: i32,
|
||||||
let mut files = Vec::new();
|
) -> Result<Page<models::ChangedFile>, String> {
|
||||||
for page in 1.. {
|
apis::repository_api::repo_get_pull_request_files(
|
||||||
let batch = apis::repository_api::repo_get_pull_request_files(
|
configuration,
|
||||||
configuration,
|
owner,
|
||||||
owner,
|
repository,
|
||||||
repository,
|
number,
|
||||||
number,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
Some(page),
|
||||||
Some(page),
|
Some(PAGE_SIZE),
|
||||||
Some(100),
|
)
|
||||||
)
|
.await
|
||||||
.await
|
.map(Page::from_items)
|
||||||
.map_err(|error| error.to_string())?;
|
.map_err(|error| error.to_string())
|
||||||
let done = batch.len() < 100;
|
|
||||||
files.extend(batch);
|
|
||||||
if done {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(files)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_issue(
|
pub async fn load_issue(
|
||||||
@@ -416,20 +461,37 @@ pub async fn load_issue(
|
|||||||
owner: &str,
|
owner: &str,
|
||||||
repository: &str,
|
repository: &str,
|
||||||
number: i64,
|
number: i64,
|
||||||
|
page: i32,
|
||||||
) -> Result<IssueDetails, String> {
|
) -> Result<IssueDetails, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
let configuration = client.configuration();
|
let configuration = client.configuration();
|
||||||
let (issue, comments) = tokio::join!(
|
let (issue, comments) = tokio::join!(
|
||||||
apis::issue_api::issue_get_issue(&configuration, owner, repository, number),
|
apis::issue_api::issue_get_issue(&configuration, owner, repository, number),
|
||||||
apis::issue_api::issue_get_comments(&configuration, owner, repository, number, None, None,),
|
load_issue_comments(&configuration, owner, repository, number, page),
|
||||||
);
|
);
|
||||||
Ok(IssueDetails {
|
Ok(IssueDetails {
|
||||||
issue: issue.map_err(|error| error.to_string())?,
|
issue: issue.map_err(|error| error.to_string())?,
|
||||||
comments: comments.map_err(|error| error.to_string())?,
|
has_more: false,
|
||||||
|
comments: comments?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn load_issue_comments(
|
||||||
|
configuration: &apis::configuration::Configuration,
|
||||||
|
owner: &str,
|
||||||
|
repository: &str,
|
||||||
|
number: i64,
|
||||||
|
page: i32,
|
||||||
|
) -> Result<Vec<models::Comment>, String> {
|
||||||
|
if page > 1 {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
apis::issue_api::issue_get_comments(configuration, owner, repository, number, None, None)
|
||||||
|
.await
|
||||||
|
.map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn load_issue_editor(
|
pub async fn load_issue_editor(
|
||||||
server: &Server,
|
server: &Server,
|
||||||
owner: &str,
|
owner: &str,
|
||||||
@@ -544,19 +606,22 @@ pub async fn load_pull(
|
|||||||
owner: &str,
|
owner: &str,
|
||||||
repository: &str,
|
repository: &str,
|
||||||
number: i64,
|
number: i64,
|
||||||
|
page: i32,
|
||||||
) -> Result<PullDetails, String> {
|
) -> Result<PullDetails, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
let configuration = client.configuration();
|
let configuration = client.configuration();
|
||||||
let (pull, comments, files) = tokio::join!(
|
let (pull, comments, files) = tokio::join!(
|
||||||
apis::repository_api::repo_get_pull_request(&configuration, owner, repository, number,),
|
apis::repository_api::repo_get_pull_request(&configuration, owner, repository, number,),
|
||||||
apis::issue_api::issue_get_comments(&configuration, owner, repository, number, None, None,),
|
load_issue_comments(&configuration, owner, repository, number, page),
|
||||||
load_pull_files(&configuration, owner, repository, number),
|
load_pull_files(&configuration, owner, repository, number, page),
|
||||||
);
|
);
|
||||||
|
let files = files?;
|
||||||
Ok(PullDetails {
|
Ok(PullDetails {
|
||||||
pull: pull.map_err(|error| error.to_string())?,
|
pull: pull.map_err(|error| error.to_string())?,
|
||||||
comments: comments.map_err(|error| error.to_string())?,
|
has_more: files.has_more,
|
||||||
files: files.map_err(|error| error.to_string())?,
|
comments: comments?,
|
||||||
|
files: files.items,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,12 +886,14 @@ async fn load_commits_for_ref(
|
|||||||
owner: &str,
|
owner: &str,
|
||||||
repository: &str,
|
repository: &str,
|
||||||
branch: Option<&str>,
|
branch: Option<&str>,
|
||||||
) -> Result<Vec<models::Commit>, String> {
|
pages: u32,
|
||||||
|
) -> Result<Page<models::Commit>, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
let configuration = client.configuration();
|
let configuration = client.configuration();
|
||||||
let mut commits = Vec::new();
|
let mut commits = Vec::new();
|
||||||
for page in 1.. {
|
let mut has_more = false;
|
||||||
|
for page in 1..=pages.max(1) {
|
||||||
let batch = apis::repository_api::repo_get_all_commits(
|
let batch = apis::repository_api::repo_get_all_commits(
|
||||||
&configuration,
|
&configuration,
|
||||||
owner,
|
owner,
|
||||||
@@ -838,22 +905,25 @@ async fn load_commits_for_ref(
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Some(false),
|
Some(false),
|
||||||
Some(page),
|
Some(page as i32),
|
||||||
Some(100),
|
Some(PAGE_SIZE),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| error.to_string())?;
|
.map_err(|error| error.to_string())?;
|
||||||
let done = batch.len() < 100;
|
has_more = batch.len() == PAGE_SIZE as usize;
|
||||||
commits.extend(batch);
|
commits.extend(batch);
|
||||||
if done {
|
if !has_more {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(commits)
|
Ok(Page {
|
||||||
|
items: commits,
|
||||||
|
has_more,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_home(server: &Server) -> Result<HomeData, String> {
|
pub async fn load_home(server: &Server, page: i32) -> Result<HomeData, String> {
|
||||||
let client =
|
let client =
|
||||||
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?;
|
||||||
let configuration = client.configuration();
|
let configuration = client.configuration();
|
||||||
@@ -863,34 +933,23 @@ pub async fn load_home(server: &Server) -> Result<HomeData, String> {
|
|||||||
.map_err(|error| error.to_string())?
|
.map_err(|error| error.to_string())?
|
||||||
.login
|
.login
|
||||||
.ok_or("The server account has no username.")?;
|
.ok_or("The server account has no username.")?;
|
||||||
let activity_configuration = configuration.clone();
|
let activities = apis::user_api::user_list_activity_feeds(
|
||||||
let activity_login = login.clone();
|
&configuration,
|
||||||
let activities = async move {
|
&login,
|
||||||
let mut activities = Vec::new();
|
Some(true),
|
||||||
for page in 1.. {
|
None,
|
||||||
let batch = apis::user_api::user_list_activity_feeds(
|
Some(page),
|
||||||
&activity_configuration,
|
Some(PAGE_SIZE),
|
||||||
&activity_login,
|
);
|
||||||
Some(true),
|
|
||||||
None,
|
|
||||||
Some(page),
|
|
||||||
Some(100),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|error| error.to_string())?;
|
|
||||||
if batch.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
activities.extend(batch);
|
|
||||||
}
|
|
||||||
Ok::<_, String>(activities)
|
|
||||||
};
|
|
||||||
let (activities, heatmap) = tokio::join!(
|
let (activities, heatmap) = tokio::join!(
|
||||||
activities,
|
activities,
|
||||||
apis::user_api::user_get_heatmap_data(&configuration, &login),
|
apis::user_api::user_get_heatmap_data(&configuration, &login),
|
||||||
);
|
);
|
||||||
Ok(HomeData {
|
Ok(HomeData {
|
||||||
activities: activities?,
|
has_more: activities
|
||||||
|
.as_ref()
|
||||||
|
.is_ok_and(|items| items.len() == PAGE_SIZE as usize),
|
||||||
|
activities: activities.map_err(|error| error.to_string())?,
|
||||||
heatmap: heatmap.map_err(|error| error.to_string())?,
|
heatmap: heatmap.map_err(|error| error.to_string())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,22 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||||||
use gotcha_gitea::models;
|
use gotcha_gitea::models;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub const PAGE_SIZE: i32 = 30;
|
||||||
|
|
||||||
|
pub struct Page<T> {
|
||||||
|
pub items: Vec<T>,
|
||||||
|
pub has_more: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Page<T> {
|
||||||
|
pub fn from_items(items: Vec<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
has_more: items.len() == PAGE_SIZE as usize,
|
||||||
|
items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum AppearanceMode {
|
pub enum AppearanceMode {
|
||||||
@@ -132,11 +148,13 @@ pub struct State {
|
|||||||
pub struct HomeData {
|
pub struct HomeData {
|
||||||
pub activities: Vec<models::Activity>,
|
pub activities: Vec<models::Activity>,
|
||||||
pub heatmap: Vec<models::UserHeatmapData>,
|
pub heatmap: Vec<models::UserHeatmapData>,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IssueDetails {
|
pub struct IssueDetails {
|
||||||
pub issue: models::Issue,
|
pub issue: models::Issue,
|
||||||
pub comments: Vec<models::Comment>,
|
pub comments: Vec<models::Comment>,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IssueEditorData {
|
pub struct IssueEditorData {
|
||||||
@@ -157,12 +175,14 @@ pub struct MilestoneDetails {
|
|||||||
pub milestone: models::Milestone,
|
pub milestone: models::Milestone,
|
||||||
pub issues: Vec<models::Issue>,
|
pub issues: Vec<models::Issue>,
|
||||||
pub pulls: Vec<models::Issue>,
|
pub pulls: Vec<models::Issue>,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PullDetails {
|
pub struct PullDetails {
|
||||||
pub pull: models::PullRequest,
|
pub pull: models::PullRequest,
|
||||||
pub comments: Vec<models::Comment>,
|
pub comments: Vec<models::Comment>,
|
||||||
pub files: Vec<models::ChangedFile>,
|
pub files: Vec<models::ChangedFile>,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_status() -> String {
|
pub fn open_status() -> String {
|
||||||
@@ -244,4 +264,10 @@ mod tests {
|
|||||||
assert_eq!(parse_api_date("2023-02-29T00:00:00Z"), None);
|
assert_eq!(parse_api_date("2023-02-29T00:00:00Z"), None);
|
||||||
assert_eq!(parse_api_date("not-a-date"), None);
|
assert_eq!(parse_api_date("not-a-date"), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_api_pages_expose_more_results() {
|
||||||
|
assert!(Page::from_items(vec![(); PAGE_SIZE as usize]).has_more);
|
||||||
|
assert!(!Page::from_items(vec![(); PAGE_SIZE as usize - 1]).has_more);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,18 +173,36 @@ impl GotchaCore {
|
|||||||
save_preferences(&state.preferences).map_err(Into::into)
|
save_preferences(&state.preferences).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn home(&self) -> Result<HomePage, GotchaError> {
|
pub async fn home(&self, page: u32) -> Result<HomePage, GotchaError> {
|
||||||
let server = self.server()?;
|
let server = self.server()?;
|
||||||
let name = server.name.clone();
|
let name = server.name.clone();
|
||||||
Ok(home_page(name, load_home(&server).await?))
|
Ok(home_page(
|
||||||
|
name,
|
||||||
|
load_home(&server, valid_page(page)?).await?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn repositories(&self) -> Result<Vec<RepositoryRow>, GotchaError> {
|
pub async fn repositories(&self, page: u32) -> Result<RepositoryListPage, GotchaError> {
|
||||||
let server = self.server()?;
|
let server = self.server()?;
|
||||||
let repositories = load_repositories(&server).await?;
|
let page_number = valid_page(page)?;
|
||||||
|
let repositories = load_repositories(&server, page_number).await?;
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
state.repositories = repositories;
|
if page_number == 1 {
|
||||||
Ok(self.repository_rows(&state))
|
state.repositories.clear();
|
||||||
|
}
|
||||||
|
for repository in repositories.items {
|
||||||
|
if let Some(existing) = state.repositories.iter_mut().find(|candidate| {
|
||||||
|
candidate.owner == repository.owner && candidate.name == repository.name
|
||||||
|
}) {
|
||||||
|
*existing = repository;
|
||||||
|
} else {
|
||||||
|
state.repositories.push(repository);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(RepositoryListPage {
|
||||||
|
rows: self.repository_rows(&state),
|
||||||
|
has_more: repositories.has_more,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_favorite(
|
pub fn toggle_favorite(
|
||||||
@@ -209,7 +227,8 @@ impl GotchaCore {
|
|||||||
&self,
|
&self,
|
||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
) -> Result<Vec<IssueRow>, GotchaError> {
|
page: u32,
|
||||||
|
) -> Result<IssueListPage, GotchaError> {
|
||||||
let server = self.server()?;
|
let server = self.server()?;
|
||||||
let (status, filter) = {
|
let (status, filter) = {
|
||||||
let state = self.state.lock().unwrap();
|
let state = self.state.lock().unwrap();
|
||||||
@@ -224,17 +243,20 @@ impl GotchaCore {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
let labels: Vec<_> = filter.labels.into_iter().collect();
|
let labels: Vec<_> = filter.labels.into_iter().collect();
|
||||||
Ok(issue_rows(
|
let page = load_issues(
|
||||||
&load_issues(
|
&server,
|
||||||
&server,
|
&owner,
|
||||||
&owner,
|
&repository,
|
||||||
&repository,
|
&status,
|
||||||
&status,
|
&labels,
|
||||||
&labels,
|
&filter.milestone,
|
||||||
&filter.milestone,
|
valid_page(page)?,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?;
|
||||||
))
|
Ok(IssueListPage {
|
||||||
|
rows: issue_rows(&page.items),
|
||||||
|
has_more: page.has_more,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn issue_filters(
|
pub async fn issue_filters(
|
||||||
@@ -317,9 +339,17 @@ impl GotchaCore {
|
|||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
number: i64,
|
number: i64,
|
||||||
|
page: u32,
|
||||||
) -> Result<IssuePage, GotchaError> {
|
) -> Result<IssuePage, GotchaError> {
|
||||||
Ok(issue_page(
|
Ok(issue_page(
|
||||||
load_issue(&self.server()?, &owner, &repository, number).await?,
|
load_issue(
|
||||||
|
&self.server()?,
|
||||||
|
&owner,
|
||||||
|
&repository,
|
||||||
|
number,
|
||||||
|
valid_page(page)?,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,10 +417,14 @@ impl GotchaCore {
|
|||||||
&self,
|
&self,
|
||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
) -> Result<Vec<MilestoneRow>, GotchaError> {
|
page: u32,
|
||||||
Ok(milestone_rows(
|
) -> Result<MilestoneListPage, GotchaError> {
|
||||||
&load_milestones(&self.server()?, &owner, &repository).await?,
|
let page =
|
||||||
))
|
load_milestones_page(&self.server()?, &owner, &repository, valid_page(page)?).await?;
|
||||||
|
Ok(MilestoneListPage {
|
||||||
|
rows: milestone_rows(&page.items),
|
||||||
|
has_more: page.has_more,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn milestone(
|
pub async fn milestone(
|
||||||
@@ -398,9 +432,10 @@ impl GotchaCore {
|
|||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
id: i64,
|
id: i64,
|
||||||
|
page: u32,
|
||||||
) -> Result<MilestonePage, GotchaError> {
|
) -> Result<MilestonePage, GotchaError> {
|
||||||
Ok(milestone_page(
|
Ok(milestone_page(
|
||||||
load_milestone(&self.server()?, &owner, &repository, id).await?,
|
load_milestone(&self.server()?, &owner, &repository, id, valid_page(page)?).await?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +484,7 @@ impl GotchaCore {
|
|||||||
save_preferences(&state.preferences).map_err(Into::into)
|
save_preferences(&state.preferences).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pulls(&self) -> Result<Vec<PullRow>, GotchaError> {
|
pub async fn pulls(&self, page: u32) -> Result<PullListPage, GotchaError> {
|
||||||
let server = self.server()?;
|
let server = self.server()?;
|
||||||
let (status, filter) = {
|
let (status, filter) = {
|
||||||
let state = self.state.lock().unwrap();
|
let state = self.state.lock().unwrap();
|
||||||
@@ -463,9 +498,11 @@ impl GotchaCore {
|
|||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
Ok(pull_rows(
|
let page = load_pulls(&server, &status, &filter.milestone, valid_page(page)?).await?;
|
||||||
&load_pulls(&server, &status, &filter.milestone).await?,
|
Ok(PullListPage {
|
||||||
))
|
rows: pull_rows(&page.items),
|
||||||
|
has_more: page.has_more,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pull(
|
pub async fn pull(
|
||||||
@@ -473,9 +510,17 @@ impl GotchaCore {
|
|||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
number: i64,
|
number: i64,
|
||||||
|
page: u32,
|
||||||
) -> Result<PullPage, GotchaError> {
|
) -> Result<PullPage, GotchaError> {
|
||||||
Ok(pull_page(
|
Ok(pull_page(
|
||||||
load_pull(&self.server()?, &owner, &repository, number).await?,
|
load_pull(
|
||||||
|
&self.server()?,
|
||||||
|
&owner,
|
||||||
|
&repository,
|
||||||
|
number,
|
||||||
|
valid_page(page)?,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +529,7 @@ impl GotchaCore {
|
|||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
branch: Option<String>,
|
branch: Option<String>,
|
||||||
|
pages: u32,
|
||||||
) -> Result<CommitPage, GotchaError> {
|
) -> Result<CommitPage, GotchaError> {
|
||||||
let server = self.server()?;
|
let server = self.server()?;
|
||||||
let default_branch = self
|
let default_branch = self
|
||||||
@@ -497,10 +543,17 @@ impl GotchaCore {
|
|||||||
.unwrap_or_else(|| "main".into());
|
.unwrap_or_else(|| "main".into());
|
||||||
let branches = load_branches(&server, &owner, &repository, &default_branch).await?;
|
let branches = load_branches(&server, &owner, &repository, &default_branch).await?;
|
||||||
let commits = match branch.as_deref() {
|
let commits = match branch.as_deref() {
|
||||||
Some(branch) => load_branch_commits(&server, &owner, &repository, branch).await?,
|
Some(branch) => {
|
||||||
None => load_all_commits(&server, &owner, &repository, &branches).await?,
|
load_branch_commits(&server, &owner, &repository, branch, pages.max(1)).await?
|
||||||
|
}
|
||||||
|
None => load_all_commits(&server, &owner, &repository, &branches, pages.max(1)).await?,
|
||||||
};
|
};
|
||||||
Ok(commit_page(branches, &commits, branch.is_none()))
|
Ok(commit_page(
|
||||||
|
branches,
|
||||||
|
&commits.items,
|
||||||
|
branch.is_none(),
|
||||||
|
commits.has_more,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn repository_contents(
|
pub async fn repository_contents(
|
||||||
@@ -574,6 +627,13 @@ fn validate_repository<'a>(
|
|||||||
Ok((owner, repository))
|
Ok((owner, repository))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn valid_page(page: u32) -> Result<i32, GotchaError> {
|
||||||
|
(page > 0)
|
||||||
|
.then(|| i32::try_from(page).ok())
|
||||||
|
.flatten()
|
||||||
|
.ok_or_else(|| "Invalid page number.".into())
|
||||||
|
}
|
||||||
|
|
||||||
impl GotchaCore {
|
impl GotchaCore {
|
||||||
fn server(&self) -> Result<Server, GotchaError> {
|
fn server(&self) -> Result<Server, GotchaError> {
|
||||||
let state = self.state.lock().unwrap();
|
let state = self.state.lock().unwrap();
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ pub struct RepositoryRow {
|
|||||||
pub favorite: bool,
|
pub favorite: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct RepositoryListPage {
|
||||||
|
pub rows: Vec<RepositoryRow>,
|
||||||
|
pub has_more: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
pub struct LabelRow {
|
pub struct LabelRow {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -40,6 +46,12 @@ pub struct IssueRow {
|
|||||||
pub labels: Vec<LabelRow>,
|
pub labels: Vec<LabelRow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct IssueListPage {
|
||||||
|
pub rows: Vec<IssueRow>,
|
||||||
|
pub has_more: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
pub struct PullRow {
|
pub struct PullRow {
|
||||||
pub number: i64,
|
pub number: i64,
|
||||||
@@ -65,6 +77,7 @@ pub struct IssuePage {
|
|||||||
pub milestone: String,
|
pub milestone: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
pub comments: Vec<CommentRow>,
|
pub comments: Vec<CommentRow>,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
@@ -115,6 +128,13 @@ pub struct MilestonePage {
|
|||||||
pub milestone: MilestoneRow,
|
pub milestone: MilestoneRow,
|
||||||
pub issues: Vec<IssueRow>,
|
pub issues: Vec<IssueRow>,
|
||||||
pub pulls: Vec<PullRow>,
|
pub pulls: Vec<PullRow>,
|
||||||
|
pub has_more: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct MilestoneListPage {
|
||||||
|
pub rows: Vec<MilestoneRow>,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
@@ -131,6 +151,13 @@ pub struct PullPage {
|
|||||||
pub files_ref: String,
|
pub files_ref: String,
|
||||||
pub files: Vec<FileRow>,
|
pub files: Vec<FileRow>,
|
||||||
pub comments: Vec<CommentRow>,
|
pub comments: Vec<CommentRow>,
|
||||||
|
pub has_more: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct PullListPage {
|
||||||
|
pub rows: Vec<PullRow>,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
@@ -149,6 +176,7 @@ pub struct CommitPage {
|
|||||||
pub branches: Vec<String>,
|
pub branches: Vec<String>,
|
||||||
pub commits: Vec<CommitRow>,
|
pub commits: Vec<CommitRow>,
|
||||||
pub lane_count: u32,
|
pub lane_count: u32,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
@@ -157,6 +185,12 @@ pub struct FileRow {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, uniffi::Record)]
|
||||||
|
pub struct FileListPage {
|
||||||
|
pub rows: Vec<FileRow>,
|
||||||
|
pub has_more: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, uniffi::Record)]
|
#[derive(Clone, uniffi::Record)]
|
||||||
pub struct RepositoryContentRow {
|
pub struct RepositoryContentRow {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -216,6 +250,7 @@ pub struct HomePage {
|
|||||||
pub pull_activities: Vec<ActivityRow>,
|
pub pull_activities: Vec<ActivityRow>,
|
||||||
pub heat_cells: Vec<HeatCell>,
|
pub heat_cells: Vec<HeatCell>,
|
||||||
pub contribution_count: i64,
|
pub contribution_count: i64,
|
||||||
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn repository_rows(
|
pub fn repository_rows(
|
||||||
@@ -323,6 +358,7 @@ pub fn milestone_page(details: MilestoneDetails) -> MilestonePage {
|
|||||||
milestone: milestone_row(&details.milestone),
|
milestone: milestone_row(&details.milestone),
|
||||||
issues: issue_rows(&details.issues),
|
issues: issue_rows(&details.issues),
|
||||||
pulls: pull_rows(&details.pulls),
|
pulls: pull_rows(&details.pulls),
|
||||||
|
has_more: details.has_more,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +425,7 @@ pub fn commit_page(
|
|||||||
branches: Vec<String>,
|
branches: Vec<String>,
|
||||||
commits: &[HistoryCommit],
|
commits: &[HistoryCommit],
|
||||||
show_graph: bool,
|
show_graph: bool,
|
||||||
|
has_more: bool,
|
||||||
) -> CommitPage {
|
) -> CommitPage {
|
||||||
let lane_count = if show_graph {
|
let lane_count = if show_graph {
|
||||||
commits
|
commits
|
||||||
@@ -454,6 +491,7 @@ pub fn commit_page(
|
|||||||
branches,
|
branches,
|
||||||
commits,
|
commits,
|
||||||
lane_count: lane_count as u32,
|
lane_count: lane_count as u32,
|
||||||
|
has_more,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,6 +515,7 @@ pub fn issue_page(details: IssueDetails) -> IssuePage {
|
|||||||
.filter(|body| !body.is_empty())
|
.filter(|body| !body.is_empty())
|
||||||
.unwrap_or_else(|| "No description provided.".into()),
|
.unwrap_or_else(|| "No description provided.".into()),
|
||||||
comments: details.comments.iter().map(comment_row).collect(),
|
comments: details.comments.iter().map(comment_row).collect(),
|
||||||
|
has_more: details.has_more,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,6 +628,7 @@ pub fn pull_page(details: PullDetails) -> PullPage {
|
|||||||
files_ref: pull_files_ref(pull),
|
files_ref: pull_files_ref(pull),
|
||||||
files: details.files.iter().filter_map(file_row).collect(),
|
files: details.files.iter().filter_map(file_row).collect(),
|
||||||
comments: details.comments.iter().map(comment_row).collect(),
|
comments: details.comments.iter().map(comment_row).collect(),
|
||||||
|
has_more: details.has_more,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,6 +762,7 @@ pub fn home_page(server_name: String, home: HomeData) -> HomePage {
|
|||||||
activities: home.activities.iter().map(activity_row).collect(),
|
activities: home.activities.iter().map(activity_row).collect(),
|
||||||
heat_cells,
|
heat_cells,
|
||||||
contribution_count,
|
contribution_count,
|
||||||
|
has_more: home.has_more,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,6 +1254,7 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
heatmap: Vec::new(),
|
heatmap: Vec::new(),
|
||||||
|
has_more: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1229,6 +1271,7 @@ mod tests {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
comments: Vec::new(),
|
comments: Vec::new(),
|
||||||
|
has_more: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(page.state, "closed");
|
assert_eq!(page.state, "closed");
|
||||||
@@ -1331,6 +1374,7 @@ mod tests {
|
|||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
|
has_more: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(page.issues.is_empty());
|
assert!(page.issues.is_empty());
|
||||||
|
|||||||
@@ -613,11 +613,11 @@ public protocol GotchaCoreProtocol: AnyObject, Sendable {
|
|||||||
|
|
||||||
func commitFiles(owner: String, repository: String, sha: String) async throws -> [FileRow]
|
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
|
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 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
|
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 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]
|
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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_commits(
|
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,
|
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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_home(
|
uniffi_gotcha_core_fn_method_gotchacore_home(
|
||||||
self.uniffiCloneHandle()
|
self.uniffiCloneHandle(),FfiConverterUInt32.lower(page)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
|
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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_issue(
|
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,
|
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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_issues(
|
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,
|
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
|
||||||
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
||||||
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
||||||
liftFunc: FfiConverterSequenceTypeIssueRow.lift,
|
liftFunc: FfiConverterTypeIssueListPage_lift,
|
||||||
errorHandler: FfiConverterTypeGotchaError_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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_milestone(
|
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,
|
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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_milestones(
|
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,
|
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
|
||||||
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
||||||
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
||||||
liftFunc: FfiConverterSequenceTypeMilestoneRow.lift,
|
liftFunc: FfiConverterTypeMilestoneListPage_lift,
|
||||||
errorHandler: FfiConverterTypeGotchaError_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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_pull(
|
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,
|
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
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_pulls(
|
uniffi_gotcha_core_fn_method_gotchacore_pulls(
|
||||||
self.uniffiCloneHandle()
|
self.uniffiCloneHandle(),FfiConverterUInt32.lower(page)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
|
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
|
||||||
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
||||||
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
||||||
liftFunc: FfiConverterSequenceTypePullRow.lift,
|
liftFunc: FfiConverterTypePullListPage_lift,
|
||||||
errorHandler: FfiConverterTypeGotchaError_lift
|
errorHandler: FfiConverterTypeGotchaError_lift
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func repositories()async throws -> [RepositoryRow] {
|
open func repositories(page: UInt32)async throws -> RepositoryListPage {
|
||||||
return
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
rustFutureFunc: {
|
rustFutureFunc: {
|
||||||
uniffi_gotcha_core_fn_method_gotchacore_repositories(
|
uniffi_gotcha_core_fn_method_gotchacore_repositories(
|
||||||
self.uniffiCloneHandle()
|
self.uniffiCloneHandle(),FfiConverterUInt32.lower(page)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
|
pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer,
|
||||||
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer,
|
||||||
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer,
|
||||||
liftFunc: FfiConverterSequenceTypeRepositoryRow.lift,
|
liftFunc: FfiConverterTypeRepositoryListPage_lift,
|
||||||
errorHandler: FfiConverterTypeGotchaError_lift
|
errorHandler: FfiConverterTypeGotchaError_lift
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1360,13 +1360,15 @@ public struct CommitPage: Equatable, Hashable {
|
|||||||
public var branches: [String]
|
public var branches: [String]
|
||||||
public var commits: [CommitRow]
|
public var commits: [CommitRow]
|
||||||
public var laneCount: UInt32
|
public var laneCount: UInt32
|
||||||
|
public var hasMore: Bool
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// 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.branches = branches
|
||||||
self.commits = commits
|
self.commits = commits
|
||||||
self.laneCount = laneCount
|
self.laneCount = laneCount
|
||||||
|
self.hasMore = hasMore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1387,7 +1389,8 @@ public struct FfiConverterTypeCommitPage: FfiConverterRustBuffer {
|
|||||||
try CommitPage(
|
try CommitPage(
|
||||||
branches: FfiConverterSequenceString.read(from: &buf),
|
branches: FfiConverterSequenceString.read(from: &buf),
|
||||||
commits: FfiConverterSequenceTypeCommitRow.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)
|
FfiConverterSequenceString.write(value.branches, into: &buf)
|
||||||
FfiConverterSequenceTypeCommitRow.write(value.commits, into: &buf)
|
FfiConverterSequenceTypeCommitRow.write(value.commits, into: &buf)
|
||||||
FfiConverterUInt32.write(value.laneCount, 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 struct FileRow: Equatable, Hashable {
|
||||||
public var path: String
|
public var path: String
|
||||||
public var status: String
|
public var status: String
|
||||||
@@ -1723,16 +1781,18 @@ public struct HomePage: Equatable, Hashable {
|
|||||||
public var pullActivities: [ActivityRow]
|
public var pullActivities: [ActivityRow]
|
||||||
public var heatCells: [HeatCell]
|
public var heatCells: [HeatCell]
|
||||||
public var contributionCount: Int64
|
public var contributionCount: Int64
|
||||||
|
public var hasMore: Bool
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// 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.serverName = serverName
|
||||||
self.activities = activities
|
self.activities = activities
|
||||||
self.issueActivities = issueActivities
|
self.issueActivities = issueActivities
|
||||||
self.pullActivities = pullActivities
|
self.pullActivities = pullActivities
|
||||||
self.heatCells = heatCells
|
self.heatCells = heatCells
|
||||||
self.contributionCount = contributionCount
|
self.contributionCount = contributionCount
|
||||||
|
self.hasMore = hasMore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1756,7 +1816,8 @@ public struct FfiConverterTypeHomePage: FfiConverterRustBuffer {
|
|||||||
issueActivities: FfiConverterSequenceTypeActivityRow.read(from: &buf),
|
issueActivities: FfiConverterSequenceTypeActivityRow.read(from: &buf),
|
||||||
pullActivities: FfiConverterSequenceTypeActivityRow.read(from: &buf),
|
pullActivities: FfiConverterSequenceTypeActivityRow.read(from: &buf),
|
||||||
heatCells: FfiConverterSequenceTypeHeatCell.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)
|
FfiConverterSequenceTypeActivityRow.write(value.pullActivities, into: &buf)
|
||||||
FfiConverterSequenceTypeHeatCell.write(value.heatCells, into: &buf)
|
FfiConverterSequenceTypeHeatCell.write(value.heatCells, into: &buf)
|
||||||
FfiConverterInt64.write(value.contributionCount, 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 struct IssuePage: Equatable, Hashable {
|
||||||
public var title: String
|
public var title: String
|
||||||
public var state: String
|
public var state: String
|
||||||
@@ -2041,16 +2157,18 @@ public struct IssuePage: Equatable, Hashable {
|
|||||||
public var milestone: String
|
public var milestone: String
|
||||||
public var body: String
|
public var body: String
|
||||||
public var comments: [CommentRow]
|
public var comments: [CommentRow]
|
||||||
|
public var hasMore: Bool
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// 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.title = title
|
||||||
self.state = state
|
self.state = state
|
||||||
self.meta = meta
|
self.meta = meta
|
||||||
self.milestone = milestone
|
self.milestone = milestone
|
||||||
self.body = body
|
self.body = body
|
||||||
self.comments = comments
|
self.comments = comments
|
||||||
|
self.hasMore = hasMore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2074,7 +2192,8 @@ public struct FfiConverterTypeIssuePage: FfiConverterRustBuffer {
|
|||||||
meta: FfiConverterString.read(from: &buf),
|
meta: FfiConverterString.read(from: &buf),
|
||||||
milestone: FfiConverterString.read(from: &buf),
|
milestone: FfiConverterString.read(from: &buf),
|
||||||
body: 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.milestone, into: &buf)
|
||||||
FfiConverterString.write(value.body, into: &buf)
|
FfiConverterString.write(value.body, into: &buf)
|
||||||
FfiConverterSequenceTypeCommentRow.write(value.comments, 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 struct MilestonePage: Equatable, Hashable {
|
||||||
public var milestone: MilestoneRow
|
public var milestone: MilestoneRow
|
||||||
public var issues: [IssueRow]
|
public var issues: [IssueRow]
|
||||||
public var pulls: [PullRow]
|
public var pulls: [PullRow]
|
||||||
|
public var hasMore: Bool
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// 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.milestone = milestone
|
||||||
self.issues = issues
|
self.issues = issues
|
||||||
self.pulls = pulls
|
self.pulls = pulls
|
||||||
|
self.hasMore = hasMore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2263,7 +2439,8 @@ public struct FfiConverterTypeMilestonePage: FfiConverterRustBuffer {
|
|||||||
try MilestonePage(
|
try MilestonePage(
|
||||||
milestone: FfiConverterTypeMilestoneRow.read(from: &buf),
|
milestone: FfiConverterTypeMilestoneRow.read(from: &buf),
|
||||||
issues: FfiConverterSequenceTypeIssueRow.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)
|
FfiConverterTypeMilestoneRow.write(value.milestone, into: &buf)
|
||||||
FfiConverterSequenceTypeIssueRow.write(value.issues, into: &buf)
|
FfiConverterSequenceTypeIssueRow.write(value.issues, into: &buf)
|
||||||
FfiConverterSequenceTypePullRow.write(value.pulls, 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 struct PullPage: Equatable, Hashable {
|
||||||
public var title: String
|
public var title: String
|
||||||
public var meta: String
|
public var meta: String
|
||||||
@@ -2425,16 +2657,18 @@ public struct PullPage: Equatable, Hashable {
|
|||||||
public var filesRef: String
|
public var filesRef: String
|
||||||
public var files: [FileRow]
|
public var files: [FileRow]
|
||||||
public var comments: [CommentRow]
|
public var comments: [CommentRow]
|
||||||
|
public var hasMore: Bool
|
||||||
|
|
||||||
// Default memberwise initializers are never public by default, so we
|
// Default memberwise initializers are never public by default, so we
|
||||||
// declare one manually.
|
// 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.title = title
|
||||||
self.meta = meta
|
self.meta = meta
|
||||||
self.body = body
|
self.body = body
|
||||||
self.filesRef = filesRef
|
self.filesRef = filesRef
|
||||||
self.files = files
|
self.files = files
|
||||||
self.comments = comments
|
self.comments = comments
|
||||||
|
self.hasMore = hasMore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2458,7 +2692,8 @@ public struct FfiConverterTypePullPage: FfiConverterRustBuffer {
|
|||||||
body: FfiConverterString.read(from: &buf),
|
body: FfiConverterString.read(from: &buf),
|
||||||
filesRef: FfiConverterString.read(from: &buf),
|
filesRef: FfiConverterString.read(from: &buf),
|
||||||
files: FfiConverterSequenceTypeFileRow.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)
|
FfiConverterString.write(value.filesRef, into: &buf)
|
||||||
FfiConverterSequenceTypeFileRow.write(value.files, into: &buf)
|
FfiConverterSequenceTypeFileRow.write(value.files, into: &buf)
|
||||||
FfiConverterSequenceTypeCommentRow.write(value.comments, 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 struct RepositoryRow: Equatable, Hashable {
|
||||||
public var name: String
|
public var name: String
|
||||||
public var owner: String
|
public var owner: String
|
||||||
@@ -3538,13 +3828,13 @@ private let initializationResult: InitializationResult = {
|
|||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_commit_files() != 43926) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_commit_files() != 43926) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_commits() != 472) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_commits() != 59625) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_home() != 5983) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_home() != 38990) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_issue() != 56735) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_issue() != 3108) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_issue_editor() != 10078) {
|
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) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_issue_filters_active() != 51470) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_issues() != 1316) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_issues() != 8566) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_milestone() != 26979) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_milestone() != 57186) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_milestones() != 27282) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_milestones() != 50431) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_pull() != 46939) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_pull() != 12919) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_pull_diff() != 61384) {
|
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) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_pull_filters_active() != 20260) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_pulls() != 13092) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_pulls() != 37027) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_repositories() != 12256) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_repositories() != 31164) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_repository_contents() != 8454) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_repository_contents() != 8454) {
|
||||||
|
|||||||
@@ -286,17 +286,17 @@ uint64_t uniffi_gotcha_core_fn_method_gotchacore_commit_files(uint64_t ptr, Rust
|
|||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMITS
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMITS
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_HOME
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_HOME
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE_EDITOR
|
#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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUES
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUES
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONE
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONE
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONES
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONES
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL_DIFF
|
#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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULLS
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULLS
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORIES
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORIES
|
||||||
#define 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
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORY_CONTENTS
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORY_CONTENTS
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
|||||||
private var rows: [IssueRow] = []
|
private var rows: [IssueRow] = []
|
||||||
private var filterOptions: IssueFilterOptions?
|
private var filterOptions: IssueFilterOptions?
|
||||||
private var filterTask: Task<Void, Never>?
|
private var filterTask: Task<Void, Never>?
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
private lazy var filterButton = UIBarButtonItem(
|
private lazy var filterButton = UIBarButtonItem(
|
||||||
image: context.symbol("line.3.horizontal.decrease.circle")
|
image: context.symbol("line.3.horizontal.decrease.circle")
|
||||||
)
|
)
|
||||||
@@ -45,17 +46,38 @@ final class IssuesViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func loadIssues(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
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()
|
tableView.reloadData()
|
||||||
updateEmptyState()
|
updateEmptyState()
|
||||||
} catch {
|
} 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 owner: String
|
||||||
private let repository: String
|
private let repository: String
|
||||||
private var rows: [MilestoneRow] = []
|
private var rows: [MilestoneRow] = []
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
init(context: AppContext, owner: String, repository: String) {
|
init(context: AppContext, owner: String, repository: String) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@@ -356,11 +379,29 @@ final class MilestonesViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func loadContent(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
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.reloadData()
|
||||||
tableView.backgroundView = rows.isEmpty
|
tableView.backgroundView = rows.isEmpty
|
||||||
? EmptyBackgroundView(
|
? EmptyBackgroundView(
|
||||||
@@ -369,9 +410,12 @@ final class MilestonesViewController: RefreshingTableViewController {
|
|||||||
)
|
)
|
||||||
: nil
|
: nil
|
||||||
} catch {
|
} 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 repository: String
|
||||||
private let id: Int64
|
private let id: Int64
|
||||||
private var page: MilestonePage?
|
private var page: MilestonePage?
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
init(context: AppContext, owner: String, repository: String, id: Int64) {
|
init(context: AppContext, owner: String, repository: String, id: Int64) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@@ -435,21 +480,45 @@ final class MilestoneViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func loadContent(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
do {
|
||||||
page = try await context.core.milestone(
|
let result = try await context.core.milestone(
|
||||||
owner: owner,
|
owner: owner,
|
||||||
repository: repository,
|
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
|
title = page?.milestone.title
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
} catch {
|
} 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 rows: [PullRow] = []
|
||||||
private var filterOptions: PullFilterOptions?
|
private var filterOptions: PullFilterOptions?
|
||||||
private var filterTask: Task<Void, Never>?
|
private var filterTask: Task<Void, Never>?
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@@ -626,11 +696,25 @@ final class PullsViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func loadPulls(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
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()
|
tableView.reloadData()
|
||||||
let status = context.core.settings().pullStatus
|
let status = context.core.settings().pullStatus
|
||||||
tableView.backgroundView = rows.isEmpty
|
tableView.backgroundView = rows.isEmpty
|
||||||
@@ -642,9 +726,12 @@ final class PullsViewController: RefreshingTableViewController {
|
|||||||
)
|
)
|
||||||
: nil
|
: nil
|
||||||
} catch {
|
} 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 contents: [RepositoryContentRow] = []
|
||||||
private var branch: String?
|
private var branch: String?
|
||||||
private var mode = Mode.history
|
private var mode = Mode.history
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
init(context: AppContext, owner: String, repository: String) {
|
init(context: AppContext, owner: String, repository: String) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@@ -808,7 +896,19 @@ final class CommitsViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func loadContent(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
do {
|
||||||
@@ -817,8 +917,11 @@ final class CommitsViewController: RefreshingTableViewController {
|
|||||||
page = try await context.core.commits(
|
page = try await context.core.commits(
|
||||||
owner: owner,
|
owner: owner,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
branch: branch
|
branch: branch,
|
||||||
|
pages: requestedPage
|
||||||
)
|
)
|
||||||
|
currentPage = requestedPage
|
||||||
|
finishPagination(hasMore: page?.hasMore ?? false)
|
||||||
updateBranchMenu()
|
updateBranchMenu()
|
||||||
case .files:
|
case .files:
|
||||||
contents = try await context.core.repositoryContents(
|
contents = try await context.core.repositoryContents(
|
||||||
@@ -830,9 +933,12 @@ final class CommitsViewController: RefreshingTableViewController {
|
|||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
updateEmptyView()
|
updateEmptyView()
|
||||||
} catch {
|
} 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
|
import UIKit
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class MarkdownPageViewController: UIViewController {
|
class MarkdownPageViewController: UIViewController, UIScrollViewDelegate {
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let scrollView = UIScrollView()
|
let scrollView = UIScrollView()
|
||||||
let stack = UIStackView()
|
let stack = UIStackView()
|
||||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||||
var loadingTask: Task<Void, Never>?
|
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) {
|
init(context: AppContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@@ -25,6 +31,7 @@ class MarkdownPageViewController: UIViewController {
|
|||||||
view.backgroundColor = .systemGroupedBackground
|
view.backgroundColor = .systemGroupedBackground
|
||||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
scrollView.alwaysBounceVertical = true
|
scrollView.alwaysBounceVertical = true
|
||||||
|
scrollView.delegate = self
|
||||||
scrollView.refreshControl = UIRefreshControl()
|
scrollView.refreshControl = UIRefreshControl()
|
||||||
scrollView.refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged)
|
scrollView.refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged)
|
||||||
view.addSubview(scrollView)
|
view.addSubview(scrollView)
|
||||||
@@ -49,6 +56,8 @@ class MarkdownPageViewController: UIViewController {
|
|||||||
|
|
||||||
func loadContent(refreshing: Bool) {}
|
func loadContent(refreshing: Bool) {}
|
||||||
|
|
||||||
|
func loadMoreContent() {}
|
||||||
|
|
||||||
func beginLoading(refreshing: Bool) {
|
func beginLoading(refreshing: Bool) {
|
||||||
if !refreshing { beginNavigationLoading(spinner) }
|
if !refreshing { beginNavigationLoading(spinner) }
|
||||||
}
|
}
|
||||||
@@ -63,6 +72,54 @@ class MarkdownPageViewController: UIViewController {
|
|||||||
views.forEach(stack.addArrangedSubview)
|
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() {
|
@objc private func refreshRequested() {
|
||||||
loadContent(refreshing: true)
|
loadContent(refreshing: true)
|
||||||
}
|
}
|
||||||
@@ -73,6 +130,8 @@ final class IssueViewController: MarkdownPageViewController {
|
|||||||
private let owner: String
|
private let owner: String
|
||||||
private let repository: String
|
private let repository: String
|
||||||
private let number: Int64
|
private let number: Int64
|
||||||
|
private var page: IssuePage?
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
init(context: AppContext, owner: String, repository: String, number: Int64) {
|
init(context: AppContext, owner: String, repository: String, number: Int64) {
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
@@ -110,27 +169,52 @@ final class IssueViewController: MarkdownPageViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func loadContent(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
do {
|
||||||
let page = try await context.core.issue(
|
let result = try await context.core.issue(
|
||||||
owner: owner,
|
owner: owner,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
number: number
|
number: number,
|
||||||
|
page: requestedPage
|
||||||
)
|
)
|
||||||
replaceContent(detailViews(
|
if requestedPage == 1 {
|
||||||
title: page.title,
|
page = result
|
||||||
issueState: page.state,
|
} else {
|
||||||
meta: page.meta,
|
page?.comments.append(contentsOf: result.comments)
|
||||||
body: page.body,
|
page?.hasMore = result.hasMore
|
||||||
comments: page.comments,
|
}
|
||||||
milestone: page.milestone
|
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 {
|
} 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 owner: String
|
||||||
private let repository: String
|
private let repository: String
|
||||||
private let number: Int64
|
private let number: Int64
|
||||||
|
private var page: PullPage?
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
init(context: AppContext, owner: String, repository: String, number: Int64) {
|
init(context: AppContext, owner: String, repository: String, number: Int64) {
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
@@ -158,24 +244,45 @@ final class PullViewController: MarkdownPageViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func loadContent(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
do {
|
||||||
let page = try await context.core.pull(
|
let result = try await context.core.pull(
|
||||||
owner: owner,
|
owner: owner,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
number: number
|
number: number,
|
||||||
|
page: requestedPage
|
||||||
)
|
)
|
||||||
var views = detailViews(
|
if requestedPage == 1 {
|
||||||
title: page.title,
|
page = result
|
||||||
meta: page.meta,
|
} else {
|
||||||
body: page.body,
|
page?.files.append(contentsOf: result.files)
|
||||||
comments: []
|
page?.comments.append(contentsOf: result.comments)
|
||||||
)
|
page?.hasMore = result.hasMore
|
||||||
if !page.files.isEmpty {
|
}
|
||||||
views.append(sectionLabel(page.filesRef))
|
currentPage = requestedPage
|
||||||
views.append(contentsOf: page.files.map { file in
|
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
|
detailButton(title: file.path, detail: file.status) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
self.navigationController?.pushViewController(
|
self.navigationController?.pushViewController(
|
||||||
@@ -191,14 +298,19 @@ final class PullViewController: MarkdownPageViewController {
|
|||||||
animated: true
|
animated: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
views.append(contentsOf: commentViews(page.comments))
|
||||||
|
replaceContent(views)
|
||||||
|
finishPagination(hasMore: page.hasMore)
|
||||||
}
|
}
|
||||||
views.append(contentsOf: commentViews(page.comments))
|
|
||||||
replaceContent(views)
|
|
||||||
} catch {
|
} 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 context: AppContext
|
||||||
private let mode: RepositoryMode
|
private let mode: RepositoryMode
|
||||||
private var rows: [RepositoryRow] = []
|
private var rows: [RepositoryRow] = []
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
init(context: AppContext, mode: RepositoryMode) {
|
init(context: AppContext, mode: RepositoryMode) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@@ -215,11 +216,25 @@ final class RepositoriesViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func loadContent(refreshing: Bool) {
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
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.reloadData()
|
||||||
tableView.backgroundView = rows.isEmpty
|
tableView.backgroundView = rows.isEmpty
|
||||||
? EmptyBackgroundView(
|
? EmptyBackgroundView(
|
||||||
@@ -228,9 +243,12 @@ final class RepositoriesViewController: RefreshingTableViewController {
|
|||||||
)
|
)
|
||||||
: nil
|
: nil
|
||||||
} catch {
|
} 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 let context: AppContext
|
||||||
private var page: HomePage?
|
private var page: HomePage?
|
||||||
private var filter = ActivityFilter.all
|
private var filter = ActivityFilter.all
|
||||||
|
private var currentPage: UInt32 = 0
|
||||||
|
|
||||||
private var activities: [ActivityRow] {
|
private var activities: [ActivityRow] {
|
||||||
guard let page else { return [] }
|
guard let page else { return [] }
|
||||||
@@ -358,24 +377,48 @@ final class HomeViewController: RefreshingTableViewController {
|
|||||||
refreshControl?.endRefreshing()
|
refreshControl?.endRefreshing()
|
||||||
return
|
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?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
do {
|
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
|
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
|
HeatmapView(page: page, selectedFilter: filter.rawValue) { [weak self] index in
|
||||||
guard let self, let filter = ActivityFilter(rawValue: index) else { return }
|
guard let self, let filter = ActivityFilter(rawValue: index) else { return }
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
self.updateActivities()
|
self.updateActivities()
|
||||||
}
|
}
|
||||||
}
|
} }
|
||||||
updateActivities()
|
updateActivities()
|
||||||
} catch {
|
} 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 {
|
class RefreshingTableViewController: UITableViewController {
|
||||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||||
var loadingTask: Task<Void, Never>?
|
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() {
|
init() {
|
||||||
super.init(style: .plain)
|
super.init(style: .plain)
|
||||||
@@ -46,6 +52,8 @@ class RefreshingTableViewController: UITableViewController {
|
|||||||
|
|
||||||
func loadContent(refreshing: Bool) {}
|
func loadContent(refreshing: Bool) {}
|
||||||
|
|
||||||
|
func loadMoreContent() {}
|
||||||
|
|
||||||
func beginLoading(refreshing: Bool) {
|
func beginLoading(refreshing: Bool) {
|
||||||
if !refreshing { beginNavigationLoading(spinner) }
|
if !refreshing { beginNavigationLoading(spinner) }
|
||||||
}
|
}
|
||||||
@@ -55,6 +63,58 @@ class RefreshingTableViewController: UITableViewController {
|
|||||||
refreshControl?.endRefreshing()
|
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() {
|
@objc private func refreshRequested() {
|
||||||
loadContent(refreshing: true)
|
loadContent(refreshing: true)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user