Show and clear active filters
This commit is contained in:
@@ -159,6 +159,10 @@ xcrun simctl launch booted de.rfc1437.gotcha
|
|||||||
selections persist independently per repository and status persists.
|
selections persist independently per repository and status persists.
|
||||||
- [ ] The issue-filter icon uses a neutral color for Open + All Milestones + no
|
- [ ] The issue-filter icon uses a neutral color for Open + All Milestones + no
|
||||||
labels, and the app accent color whenever any non-default filter is set.
|
labels, and the app accent color whenever any non-default filter is set.
|
||||||
|
- [ ] In the issue filter panel, the Status, Milestone, and Labels icons use the
|
||||||
|
app accent color independently when their filter is non-default. **Clear
|
||||||
|
Filters** restores Open + All Milestones + no labels, refreshes the rows,
|
||||||
|
returns every filter icon to neutral, and remains cleared after relaunch.
|
||||||
- [ ] Tap the add button. **New Issue** appears as a native modal with Cancel
|
- [ ] Tap the add button. **New Issue** appears as a native modal with Cancel
|
||||||
and Save, title and Markdown body fields, multi-select labels, a
|
and Save, title and Markdown body fields, multi-select labels, a
|
||||||
single-select optional milestone, and an optional inline due-date picker.
|
single-select optional milestone, and an optional inline due-date picker.
|
||||||
@@ -252,6 +256,10 @@ xcrun simctl launch booted de.rfc1437.gotcha
|
|||||||
server after relaunch.
|
server after relaunch.
|
||||||
- [ ] The pull-request filter icon uses a neutral color for Open + All
|
- [ ] The pull-request filter icon uses a neutral color for Open + All
|
||||||
Milestones and the app accent color whenever either filter is non-default.
|
Milestones and the app accent color whenever either filter is non-default.
|
||||||
|
- [ ] In the pull-request filter panel, Status and Milestone icons use the app
|
||||||
|
accent color independently when non-default. **Clear Filters** restores
|
||||||
|
Open + All Milestones, refreshes the rows, returns both icons to neutral,
|
||||||
|
and remains cleared after relaunch.
|
||||||
- [ ] Each row shows repository/number, title, author/update metadata, comment
|
- [ ] Each row shows repository/number, title, author/update metadata, comment
|
||||||
count, and draft/merged state where applicable.
|
count, and draft/merged state where applicable.
|
||||||
- [ ] Open a pull request and verify title, metadata, Markdown body, comments,
|
- [ ] Open a pull request and verify title, metadata, Markdown body, comments,
|
||||||
|
|||||||
@@ -336,6 +336,27 @@ impl GotchaCore {
|
|||||||
save_preferences(&state.preferences).map_err(Into::into)
|
save_preferences(&state.preferences).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_issue_filters(
|
||||||
|
&self,
|
||||||
|
owner: String,
|
||||||
|
repository: String,
|
||||||
|
) -> Result<(), GotchaError> {
|
||||||
|
let owner = owner.trim();
|
||||||
|
let repository = repository.trim();
|
||||||
|
if owner.is_empty() || repository.is_empty() {
|
||||||
|
return Err("Select a repository first.".into());
|
||||||
|
}
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
let server = state
|
||||||
|
.active_server
|
||||||
|
.and_then(|index| state.preferences.servers.get(index))
|
||||||
|
.ok_or("Select a server first.")?;
|
||||||
|
let key = repository_key(&server.url, owner, repository);
|
||||||
|
state.preferences.issue_status = "open".into();
|
||||||
|
state.preferences.issue_filters.remove(&key);
|
||||||
|
save_preferences(&state.preferences).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn issue(
|
pub async fn issue(
|
||||||
&self,
|
&self,
|
||||||
owner: String,
|
owner: String,
|
||||||
@@ -556,6 +577,18 @@ impl GotchaCore {
|
|||||||
save_preferences(&state.preferences).map_err(Into::into)
|
save_preferences(&state.preferences).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_pull_filters(&self) -> Result<(), GotchaError> {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
let server = state
|
||||||
|
.active_server
|
||||||
|
.and_then(|index| state.preferences.servers.get(index))
|
||||||
|
.ok_or("Select a server first.")?;
|
||||||
|
let key = server.url.clone();
|
||||||
|
state.preferences.pull_status = "open".into();
|
||||||
|
state.preferences.pull_filters.remove(&key);
|
||||||
|
save_preferences(&state.preferences).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn pulls(&self, page: u32) -> Result<PullListPage, 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) = {
|
||||||
|
|||||||
@@ -609,6 +609,10 @@ public protocol GotchaCoreProtocol: AnyObject, Sendable {
|
|||||||
|
|
||||||
func addServer(name: String, url: String, token: String) async throws -> UInt32
|
func addServer(name: String, url: String, token: String) async throws -> UInt32
|
||||||
|
|
||||||
|
func clearIssueFilters(owner: String, repository: String) throws
|
||||||
|
|
||||||
|
func clearPullFilters() throws
|
||||||
|
|
||||||
func commitDiff(owner: String, repository: String, sha: String, path: String) async throws -> DiffPage
|
func commitDiff(owner: String, repository: String, sha: String, path: String) async throws -> DiffPage
|
||||||
|
|
||||||
func commitFiles(owner: String, repository: String, sha: String) async throws -> [FileRow]
|
func commitFiles(owner: String, repository: String, sha: String) async throws -> [FileRow]
|
||||||
@@ -771,6 +775,24 @@ open func addServer(name: String, url: String, token: String)async throws -> UI
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func clearIssueFilters(owner: String, repository: String)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_gotcha_core_fn_method_gotchacore_clear_issue_filters(
|
||||||
|
self.uniffiCloneHandle(),
|
||||||
|
FfiConverterString.lower(owner),
|
||||||
|
FfiConverterString.lower(repository),uniffiCallStatus
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open func clearPullFilters()throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) {
|
||||||
|
uniffiCallStatus in
|
||||||
|
uniffi_gotcha_core_fn_method_gotchacore_clear_pull_filters(
|
||||||
|
self.uniffiCloneHandle(),uniffiCallStatus
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open func commitDiff(owner: String, repository: String, sha: String, path: String)async throws -> DiffPage {
|
open func commitDiff(owner: String, repository: String, sha: String, path: String)async throws -> DiffPage {
|
||||||
return
|
return
|
||||||
try await uniffiRustCallAsync(
|
try await uniffiRustCallAsync(
|
||||||
@@ -4015,6 +4037,12 @@ private let initializationResult: InitializationResult = {
|
|||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_add_server() != 42705) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_add_server() != 42705) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
if (uniffi_gotcha_core_checksum_method_gotchacore_clear_issue_filters() != 11092) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
|
if (uniffi_gotcha_core_checksum_method_gotchacore_clear_pull_filters() != 36676) {
|
||||||
|
return InitializationResult.apiChecksumMismatch
|
||||||
|
}
|
||||||
if (uniffi_gotcha_core_checksum_method_gotchacore_commit_diff() != 56887) {
|
if (uniffi_gotcha_core_checksum_method_gotchacore_commit_diff() != 56887) {
|
||||||
return InitializationResult.apiChecksumMismatch
|
return InitializationResult.apiChecksumMismatch
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,16 @@ RustBuffer uniffi_gotcha_core_fn_method_gotchacore_active_server_name(uint64_t p
|
|||||||
uint64_t uniffi_gotcha_core_fn_method_gotchacore_add_server(uint64_t ptr, RustBuffer name, RustBuffer url, RustBuffer token
|
uint64_t uniffi_gotcha_core_fn_method_gotchacore_add_server(uint64_t ptr, RustBuffer name, RustBuffer url, RustBuffer token
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_CLEAR_ISSUE_FILTERS
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_CLEAR_ISSUE_FILTERS
|
||||||
|
void uniffi_gotcha_core_fn_method_gotchacore_clear_issue_filters(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_CLEAR_PULL_FILTERS
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_CLEAR_PULL_FILTERS
|
||||||
|
void uniffi_gotcha_core_fn_method_gotchacore_clear_pull_filters(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||||
|
);
|
||||||
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_DIFF
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_DIFF
|
||||||
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_DIFF
|
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_DIFF
|
||||||
uint64_t uniffi_gotcha_core_fn_method_gotchacore_commit_diff(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer sha, RustBuffer path
|
uint64_t uniffi_gotcha_core_fn_method_gotchacore_commit_diff(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer sha, RustBuffer path
|
||||||
@@ -715,6 +725,18 @@ uint16_t uniffi_gotcha_core_checksum_method_gotchacore_active_server_name(void
|
|||||||
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ADD_SERVER
|
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ADD_SERVER
|
||||||
uint16_t uniffi_gotcha_core_checksum_method_gotchacore_add_server(void
|
uint16_t uniffi_gotcha_core_checksum_method_gotchacore_add_server(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_CLEAR_ISSUE_FILTERS
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_CLEAR_ISSUE_FILTERS
|
||||||
|
uint16_t uniffi_gotcha_core_checksum_method_gotchacore_clear_issue_filters(void
|
||||||
|
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_CLEAR_PULL_FILTERS
|
||||||
|
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_CLEAR_PULL_FILTERS
|
||||||
|
uint16_t uniffi_gotcha_core_checksum_method_gotchacore_clear_pull_filters(void
|
||||||
|
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMIT_DIFF
|
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMIT_DIFF
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
|||||||
let current = context.core.settings().issueStatus
|
let current = context.core.settings().issueStatus
|
||||||
let status = UIMenu(
|
let status = UIMenu(
|
||||||
title: "Status",
|
title: "Status",
|
||||||
image: context.symbol("circle.lefthalf.filled"),
|
image: filterMenuImage("circle.lefthalf.filled", active: current != "open"),
|
||||||
options: .singleSelection,
|
options: .singleSelection,
|
||||||
children: ["open", "closed"].map { status in
|
children: ["open", "closed"].map { status in
|
||||||
UIAction(
|
UIAction(
|
||||||
@@ -154,6 +154,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
|||||||
} else {
|
} else {
|
||||||
children.append(UIAction(title: "Loading filters…", attributes: .disabled) { _ in })
|
children.append(UIAction(title: "Loading filters…", attributes: .disabled) { _ in })
|
||||||
}
|
}
|
||||||
|
children.append(clearFiltersMenu())
|
||||||
filterButton.menu = UIMenu(children: children)
|
filterButton.menu = UIMenu(children: children)
|
||||||
filterButton.accessibilityLabel = "Filter issues"
|
filterButton.accessibilityLabel = "Filter issues"
|
||||||
updateFilterTint()
|
updateFilterTint()
|
||||||
@@ -172,7 +173,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
return UIMenu(
|
return UIMenu(
|
||||||
title: "Milestone",
|
title: "Milestone",
|
||||||
image: context.symbol("flag"),
|
image: filterMenuImage("flag", active: !selected.isEmpty),
|
||||||
options: .singleSelection,
|
options: .singleSelection,
|
||||||
children: [all] + actions
|
children: [all] + actions
|
||||||
)
|
)
|
||||||
@@ -203,7 +204,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
return UIMenu(
|
return UIMenu(
|
||||||
title: "Labels",
|
title: "Labels",
|
||||||
image: context.symbol("tag"),
|
image: filterMenuImage("tag", active: !selected.isEmpty),
|
||||||
children: actions.isEmpty
|
children: actions.isEmpty
|
||||||
? [UIAction(title: "No labels", attributes: .disabled) { _ in }]
|
? [UIAction(title: "No labels", attributes: .disabled) { _ in }]
|
||||||
: actions
|
: actions
|
||||||
@@ -232,6 +233,32 @@ final class IssuesViewController: RefreshingTableViewController {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func clearFiltersMenu() -> UIMenu {
|
||||||
|
UIMenu(
|
||||||
|
options: .displayInline,
|
||||||
|
children: [
|
||||||
|
UIAction(
|
||||||
|
title: "Clear Filters",
|
||||||
|
image: context.symbol("xmark.circle"),
|
||||||
|
attributes: filtersActive ? [] : .disabled
|
||||||
|
) { [weak self] _ in self?.clearFilters() },
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func clearFilters() {
|
||||||
|
filterTask?.cancel()
|
||||||
|
do {
|
||||||
|
try context.core.clearIssueFilters(owner: owner, repository: repository)
|
||||||
|
filterOptions?.selectedMilestone = ""
|
||||||
|
filterOptions?.selectedLabels = []
|
||||||
|
updateFilterMenu()
|
||||||
|
loadIssues(refreshing: false)
|
||||||
|
} catch {
|
||||||
|
show(error: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var filtersActive: Bool {
|
private var filtersActive: Bool {
|
||||||
(try? context.core.issueFiltersActive(owner: owner, repository: repository)) ?? false
|
(try? context.core.issueFiltersActive(owner: owner, repository: repository)) ?? false
|
||||||
}
|
}
|
||||||
@@ -835,7 +862,7 @@ final class PullsViewController: RefreshingTableViewController {
|
|||||||
let current = context.core.settings().pullStatus
|
let current = context.core.settings().pullStatus
|
||||||
let status = UIMenu(
|
let status = UIMenu(
|
||||||
title: "Status",
|
title: "Status",
|
||||||
image: context.symbol("circle.lefthalf.filled"),
|
image: filterMenuImage("circle.lefthalf.filled", active: current != "open"),
|
||||||
options: .singleSelection,
|
options: .singleSelection,
|
||||||
children: ["open", "closed"].map { status in
|
children: ["open", "closed"].map { status in
|
||||||
UIAction(title: status.capitalized, state: current == status ? .on : .off) {
|
UIAction(title: status.capitalized, state: current == status ? .on : .off) {
|
||||||
@@ -856,7 +883,7 @@ final class PullsViewController: RefreshingTableViewController {
|
|||||||
let item = navigationItem.rightBarButtonItem ?? UIBarButtonItem(
|
let item = navigationItem.rightBarButtonItem ?? UIBarButtonItem(
|
||||||
image: context.symbol("line.3.horizontal.decrease.circle")
|
image: context.symbol("line.3.horizontal.decrease.circle")
|
||||||
)
|
)
|
||||||
item.menu = UIMenu(children: [status, milestone])
|
item.menu = UIMenu(children: [status, milestone, clearFiltersMenu()])
|
||||||
item.accessibilityLabel = "Filter pull requests"
|
item.accessibilityLabel = "Filter pull requests"
|
||||||
navigationItem.rightBarButtonItem = item
|
navigationItem.rightBarButtonItem = item
|
||||||
updateFilterTint()
|
updateFilterTint()
|
||||||
@@ -874,7 +901,7 @@ final class PullsViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
return UIMenu(
|
return UIMenu(
|
||||||
title: "Milestone",
|
title: "Milestone",
|
||||||
image: context.symbol("flag"),
|
image: filterMenuImage("flag", active: !selected.isEmpty),
|
||||||
options: .singleSelection,
|
options: .singleSelection,
|
||||||
children: [all] + milestones
|
children: [all] + milestones
|
||||||
)
|
)
|
||||||
@@ -892,6 +919,31 @@ final class PullsViewController: RefreshingTableViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func clearFiltersMenu() -> UIMenu {
|
||||||
|
UIMenu(
|
||||||
|
options: .displayInline,
|
||||||
|
children: [
|
||||||
|
UIAction(
|
||||||
|
title: "Clear Filters",
|
||||||
|
image: context.symbol("xmark.circle"),
|
||||||
|
attributes: filtersActive ? [] : .disabled
|
||||||
|
) { [weak self] _ in self?.clearFilters() },
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func clearFilters() {
|
||||||
|
filterTask?.cancel()
|
||||||
|
do {
|
||||||
|
try context.core.clearPullFilters()
|
||||||
|
filterOptions?.selectedMilestone = ""
|
||||||
|
updateFilterMenu()
|
||||||
|
loadPulls(refreshing: false)
|
||||||
|
} catch {
|
||||||
|
show(error: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var filtersActive: Bool {
|
private var filtersActive: Bool {
|
||||||
(try? context.core.pullFiltersActive()) ?? false
|
(try? context.core.pullFiltersActive()) ?? false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,6 +201,13 @@ func configureTextCell(_ cell: UITableViewCell, title: String, detail: String, i
|
|||||||
cell.backgroundColor = .systemBackground
|
cell.backgroundColor = .systemBackground
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterMenuImage(_ symbol: String, active: Bool) -> UIImage? {
|
||||||
|
let image = UIImage(systemName: symbol)
|
||||||
|
return active
|
||||||
|
? image?.withTintColor(.systemBlue, renderingMode: .alwaysOriginal)
|
||||||
|
: image
|
||||||
|
}
|
||||||
|
|
||||||
func configureRepositoryContentCell(_ cell: UITableViewCell, row: RepositoryContentRow) {
|
func configureRepositoryContentCell(_ cell: UITableViewCell, row: RepositoryContentRow) {
|
||||||
let directory = row.kind == "dir"
|
let directory = row.kind == "dir"
|
||||||
let detail = directory
|
let detail = directory
|
||||||
|
|||||||
Reference in New Issue
Block a user