Add server-side work item search
This commit is contained in:
@@ -665,11 +665,11 @@ public protocol GotchaCoreProtocol: AnyObject, Sendable {
|
||||
|
||||
func setAppearance(index: UInt32) throws
|
||||
|
||||
func setIssueFilters(owner: String, repository: String, milestone: String, labels: [String]) throws
|
||||
func setIssueFilters(owner: String, repository: String, milestone: String, labels: [String], searchText: String) throws
|
||||
|
||||
func setIssueStatus(status: String) throws
|
||||
|
||||
func setPullFilters(milestone: String) throws
|
||||
func setPullFilters(milestone: String, searchText: String) throws
|
||||
|
||||
func setPullStatus(status: String) throws
|
||||
|
||||
@@ -1176,14 +1176,15 @@ open func setAppearance(index: UInt32)throws {try rustCallWithError(FfiConvert
|
||||
}
|
||||
}
|
||||
|
||||
open func setIssueFilters(owner: String, repository: String, milestone: String, labels: [String])throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) {
|
||||
open func setIssueFilters(owner: String, repository: String, milestone: String, labels: [String], searchText: String)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_gotcha_core_fn_method_gotchacore_set_issue_filters(
|
||||
self.uniffiCloneHandle(),
|
||||
FfiConverterString.lower(owner),
|
||||
FfiConverterString.lower(repository),
|
||||
FfiConverterString.lower(milestone),
|
||||
FfiConverterSequenceString.lower(labels),uniffiCallStatus
|
||||
FfiConverterSequenceString.lower(labels),
|
||||
FfiConverterString.lower(searchText),uniffiCallStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1197,11 +1198,12 @@ open func setIssueStatus(status: String)throws {try rustCallWithError(FfiConve
|
||||
}
|
||||
}
|
||||
|
||||
open func setPullFilters(milestone: String)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) {
|
||||
open func setPullFilters(milestone: String, searchText: String)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_gotcha_core_fn_method_gotchacore_set_pull_filters(
|
||||
self.uniffiCloneHandle(),
|
||||
FfiConverterString.lower(milestone),uniffiCallStatus
|
||||
FfiConverterString.lower(milestone),
|
||||
FfiConverterString.lower(searchText),uniffiCallStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2117,15 +2119,17 @@ public struct IssueFilterOptions: Equatable, Hashable {
|
||||
public var unavailableLabels: [String]
|
||||
public var selectedMilestone: String
|
||||
public var selectedLabels: [String]
|
||||
public var searchText: String
|
||||
|
||||
// Default memberwise initializers are never public by default, so we
|
||||
// declare one manually.
|
||||
public init(milestones: [String], labels: [String], unavailableLabels: [String], selectedMilestone: String, selectedLabels: [String]) {
|
||||
public init(milestones: [String], labels: [String], unavailableLabels: [String], selectedMilestone: String, selectedLabels: [String], searchText: String) {
|
||||
self.milestones = milestones
|
||||
self.labels = labels
|
||||
self.unavailableLabels = unavailableLabels
|
||||
self.selectedMilestone = selectedMilestone
|
||||
self.selectedLabels = selectedLabels
|
||||
self.searchText = searchText
|
||||
}
|
||||
|
||||
|
||||
@@ -2148,7 +2152,8 @@ public struct FfiConverterTypeIssueFilterOptions: FfiConverterRustBuffer {
|
||||
labels: FfiConverterSequenceString.read(from: &buf),
|
||||
unavailableLabels: FfiConverterSequenceString.read(from: &buf),
|
||||
selectedMilestone: FfiConverterString.read(from: &buf),
|
||||
selectedLabels: FfiConverterSequenceString.read(from: &buf)
|
||||
selectedLabels: FfiConverterSequenceString.read(from: &buf),
|
||||
searchText: FfiConverterString.read(from: &buf)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2158,6 +2163,7 @@ public struct FfiConverterTypeIssueFilterOptions: FfiConverterRustBuffer {
|
||||
FfiConverterSequenceString.write(value.unavailableLabels, into: &buf)
|
||||
FfiConverterString.write(value.selectedMilestone, into: &buf)
|
||||
FfiConverterSequenceString.write(value.selectedLabels, into: &buf)
|
||||
FfiConverterString.write(value.searchText, into: &buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2696,12 +2702,14 @@ public func FfiConverterTypeMilestoneRow_lower(_ value: MilestoneRow) -> RustBuf
|
||||
public struct PullFilterOptions: Equatable, Hashable {
|
||||
public var milestones: [String]
|
||||
public var selectedMilestone: String
|
||||
public var searchText: String
|
||||
|
||||
// Default memberwise initializers are never public by default, so we
|
||||
// declare one manually.
|
||||
public init(milestones: [String], selectedMilestone: String) {
|
||||
public init(milestones: [String], selectedMilestone: String, searchText: String) {
|
||||
self.milestones = milestones
|
||||
self.selectedMilestone = selectedMilestone
|
||||
self.searchText = searchText
|
||||
}
|
||||
|
||||
|
||||
@@ -2721,13 +2729,15 @@ public struct FfiConverterTypePullFilterOptions: FfiConverterRustBuffer {
|
||||
return
|
||||
try PullFilterOptions(
|
||||
milestones: FfiConverterSequenceString.read(from: &buf),
|
||||
selectedMilestone: FfiConverterString.read(from: &buf)
|
||||
selectedMilestone: FfiConverterString.read(from: &buf),
|
||||
searchText: FfiConverterString.read(from: &buf)
|
||||
)
|
||||
}
|
||||
|
||||
public static func write(_ value: PullFilterOptions, into buf: inout [UInt8]) {
|
||||
FfiConverterSequenceString.write(value.milestones, into: &buf)
|
||||
FfiConverterString.write(value.selectedMilestone, into: &buf)
|
||||
FfiConverterString.write(value.searchText, into: &buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4211,13 +4221,13 @@ private let initializationResult: InitializationResult = {
|
||||
if (uniffi_gotcha_core_checksum_method_gotchacore_set_appearance() != 61293) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_gotcha_core_checksum_method_gotchacore_set_issue_filters() != 45204) {
|
||||
if (uniffi_gotcha_core_checksum_method_gotchacore_set_issue_filters() != 24891) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_gotcha_core_checksum_method_gotchacore_set_issue_status() != 44445) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_gotcha_core_checksum_method_gotchacore_set_pull_filters() != 39023) {
|
||||
if (uniffi_gotcha_core_checksum_method_gotchacore_set_pull_filters() != 25211) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_gotcha_core_checksum_method_gotchacore_set_pull_status() != 1356) {
|
||||
|
||||
@@ -416,7 +416,7 @@ void uniffi_gotcha_core_fn_method_gotchacore_set_appearance(uint64_t ptr, uint32
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_ISSUE_FILTERS
|
||||
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_ISSUE_FILTERS
|
||||
void uniffi_gotcha_core_fn_method_gotchacore_set_issue_filters(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer milestone, RustBuffer labels, RustCallStatus *_Nonnull out_status
|
||||
void uniffi_gotcha_core_fn_method_gotchacore_set_issue_filters(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer milestone, RustBuffer labels, RustBuffer search_text, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_ISSUE_STATUS
|
||||
@@ -426,7 +426,7 @@ void uniffi_gotcha_core_fn_method_gotchacore_set_issue_status(uint64_t ptr, Rust
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_PULL_FILTERS
|
||||
#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_PULL_FILTERS
|
||||
void uniffi_gotcha_core_fn_method_gotchacore_set_pull_filters(uint64_t ptr, RustBuffer milestone, RustCallStatus *_Nonnull out_status
|
||||
void uniffi_gotcha_core_fn_method_gotchacore_set_pull_filters(uint64_t ptr, RustBuffer milestone, RustBuffer search_text, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_PULL_STATUS
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
import UIKit
|
||||
|
||||
private extension UIViewController {
|
||||
func promptForSearchText(title: String, current: String, apply: @escaping (String) -> Void) {
|
||||
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
|
||||
alert.addTextField { field in
|
||||
field.text = current
|
||||
field.placeholder = "Search text"
|
||||
field.accessibilityLabel = "Search text"
|
||||
field.clearButtonMode = .whileEditing
|
||||
field.returnKeyType = .search
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "Apply", style: .default) { [weak alert] _ in
|
||||
apply(alert?.textFields?.first?.text ?? "")
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class IssuesViewController: RefreshingTableViewController {
|
||||
private let context: AppContext
|
||||
@@ -149,6 +167,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
)
|
||||
var children: [UIMenuElement] = [status]
|
||||
if let filterOptions {
|
||||
children.insert(searchAction(filterOptions), at: 0)
|
||||
children.append(milestoneMenu(filterOptions))
|
||||
children.append(labelMenu(filterOptions))
|
||||
} else {
|
||||
@@ -160,6 +179,21 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
updateFilterTint()
|
||||
}
|
||||
|
||||
private func searchAction(_ options: IssueFilterOptions) -> UIAction {
|
||||
UIAction(
|
||||
title: "Search Text",
|
||||
subtitle: options.searchText.isEmpty ? "Any text" : options.searchText,
|
||||
image: filterMenuImage("magnifyingglass", active: !options.searchText.isEmpty)
|
||||
) { [weak self] _ in
|
||||
self?.promptForSearchText(
|
||||
title: "Search Issues",
|
||||
current: options.searchText
|
||||
) { [weak self] searchText in
|
||||
self?.setSearchText(searchText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func milestoneMenu(_ options: IssueFilterOptions) -> UIMenu {
|
||||
let selected = options.selectedMilestone
|
||||
let all = UIAction(title: "All Milestones", state: selected.isEmpty ? .on : .off) {
|
||||
@@ -192,7 +226,11 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
var labels = Set(options.selectedLabels)
|
||||
if labels.remove(label) == nil { labels.insert(label) }
|
||||
do {
|
||||
try self.saveFilters(milestone: options.selectedMilestone, labels: labels)
|
||||
try self.saveFilters(
|
||||
milestone: options.selectedMilestone,
|
||||
labels: labels,
|
||||
searchText: options.searchText
|
||||
)
|
||||
self.filterOptions?.selectedLabels = Array(labels)
|
||||
action.state = labels.contains(label) ? .on : .off
|
||||
self.updateFilterMenu()
|
||||
@@ -215,7 +253,11 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
guard let options = filterOptions else { return }
|
||||
filterTask?.cancel()
|
||||
do {
|
||||
try saveFilters(milestone: milestone, labels: Set(options.selectedLabels))
|
||||
try saveFilters(
|
||||
milestone: milestone,
|
||||
labels: Set(options.selectedLabels),
|
||||
searchText: options.searchText
|
||||
)
|
||||
filterOptions?.selectedMilestone = milestone
|
||||
updateFilterMenu()
|
||||
loadIssues(refreshing: false)
|
||||
@@ -224,12 +266,34 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func saveFilters(milestone: String, labels: Set<String>) throws {
|
||||
private func setSearchText(_ searchText: String) {
|
||||
guard let options = filterOptions else { return }
|
||||
filterTask?.cancel()
|
||||
do {
|
||||
try saveFilters(
|
||||
milestone: options.selectedMilestone,
|
||||
labels: Set(options.selectedLabels),
|
||||
searchText: searchText
|
||||
)
|
||||
filterOptions?.searchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
updateFilterMenu()
|
||||
loadIssues(refreshing: false)
|
||||
} catch {
|
||||
show(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveFilters(
|
||||
milestone: String,
|
||||
labels: Set<String>,
|
||||
searchText: String
|
||||
) throws {
|
||||
try context.core.setIssueFilters(
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
milestone: milestone,
|
||||
labels: Array(labels)
|
||||
labels: Array(labels),
|
||||
searchText: searchText
|
||||
)
|
||||
}
|
||||
|
||||
@@ -252,6 +316,7 @@ final class IssuesViewController: RefreshingTableViewController {
|
||||
try context.core.clearIssueFilters(owner: owner, repository: repository)
|
||||
filterOptions?.selectedMilestone = ""
|
||||
filterOptions?.selectedLabels = []
|
||||
filterOptions?.searchText = ""
|
||||
updateFilterMenu()
|
||||
loadIssues(refreshing: false)
|
||||
} catch {
|
||||
@@ -930,12 +995,33 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
let item = navigationItem.rightBarButtonItem ?? UIBarButtonItem(
|
||||
image: context.symbol("line.3.horizontal.decrease.circle")
|
||||
)
|
||||
item.menu = UIMenu(children: [status, milestone, clearFiltersMenu()])
|
||||
var children: [UIMenuElement] = [status]
|
||||
if let filterOptions {
|
||||
children.insert(searchAction(filterOptions), at: 0)
|
||||
}
|
||||
children.append(milestone)
|
||||
children.append(clearFiltersMenu())
|
||||
item.menu = UIMenu(children: children)
|
||||
item.accessibilityLabel = "Filter pull requests"
|
||||
navigationItem.rightBarButtonItem = item
|
||||
updateFilterTint()
|
||||
}
|
||||
|
||||
private func searchAction(_ options: PullFilterOptions) -> UIAction {
|
||||
UIAction(
|
||||
title: "Search Text",
|
||||
subtitle: options.searchText.isEmpty ? "Any text" : options.searchText,
|
||||
image: filterMenuImage("magnifyingglass", active: !options.searchText.isEmpty)
|
||||
) { [weak self] _ in
|
||||
self?.promptForSearchText(
|
||||
title: "Search Pull Requests",
|
||||
current: options.searchText
|
||||
) { [weak self] searchText in
|
||||
self?.setSearchText(searchText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func milestoneMenu(_ options: PullFilterOptions) -> UIMenu {
|
||||
let selected = options.selectedMilestone
|
||||
let all = UIAction(title: "All Milestones", state: selected.isEmpty ? .on : .off) {
|
||||
@@ -955,9 +1041,13 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
}
|
||||
|
||||
private func selectMilestone(_ milestone: String) {
|
||||
guard let options = filterOptions else { return }
|
||||
filterTask?.cancel()
|
||||
do {
|
||||
try context.core.setPullFilters(milestone: milestone)
|
||||
try context.core.setPullFilters(
|
||||
milestone: milestone,
|
||||
searchText: options.searchText
|
||||
)
|
||||
filterOptions?.selectedMilestone = milestone
|
||||
updateFilterMenu()
|
||||
loadPulls(refreshing: false)
|
||||
@@ -966,6 +1056,22 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func setSearchText(_ searchText: String) {
|
||||
guard let options = filterOptions else { return }
|
||||
filterTask?.cancel()
|
||||
do {
|
||||
try context.core.setPullFilters(
|
||||
milestone: options.selectedMilestone,
|
||||
searchText: searchText
|
||||
)
|
||||
filterOptions?.searchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
updateFilterMenu()
|
||||
loadPulls(refreshing: false)
|
||||
} catch {
|
||||
show(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func clearFiltersMenu() -> UIMenu {
|
||||
UIMenu(
|
||||
options: .displayInline,
|
||||
@@ -984,6 +1090,7 @@ final class PullsViewController: RefreshingTableViewController {
|
||||
do {
|
||||
try context.core.clearPullFilters()
|
||||
filterOptions?.selectedMilestone = ""
|
||||
filterOptions?.searchText = ""
|
||||
updateFilterMenu()
|
||||
loadPulls(refreshing: false)
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user