Fix primary navigation editing

This commit is contained in:
Georg Bauer
2026-08-15 20:18:51 +02:00
parent 59bcddfadf
commit ac6de7d4f2
4 changed files with 36 additions and 27 deletions

View File

@@ -590,16 +590,18 @@ source update are published, and installs or updates Gotcha through AltStore PAL
## Settings, accessibility, and lifecycle ## Settings, accessibility, and lifecycle
- [ ] Settings opens from the gear button on Home and is not a tab. - [ ] Settings opens from the gear button on Home and is not a tab.
- [ ] Home, Issues, Repositories, Pull Requests, Milestones, Actions, and Server - [ ] Home, Issues, Repositories, Pull Requests, Milestones, and Actions use the
Activity use the destination name as the primary navigation title and the destination name as the primary navigation title and the selected server
selected server as the smaller subtitle; changing servers updates both. as the smaller subtitle; changing servers updates both.
- [ ] Primary Navigation always keeps Home first and allows up to four ordered, - [ ] Primary Navigation always keeps Home first and allows up to four ordered,
unique choices from Issues, Repos, Pulls, Milestones, Actions, and Server unique choices from Issues, Repos, Pulls, Milestones, and Actions. Server
Activity. Add, remove, and reorder destinations with native table Activity remains only the **All users** Home timeline. Outside Edit mode,
controls; tab order updates immediately, omitted destinations remain on rows cannot add, remove, or reorder destinations. In Edit mode, native
Home, and the configuration survives relaunch and server changes. An insert, delete, and reorder controls perform all three operations; tab
upgrade from the previous build starts with Issues, Repos, Pulls, and order updates immediately, omitted destinations remain on Home, and the
Milestones. configuration survives relaunch and server changes. Existing saved
Server Activity tabs are removed during migration. An upgrade from the
previous build otherwise preserves its destination order.
- [ ] Appearance changes between Auto, Light, and Dark immediately; Auto follows - [ ] Appearance changes between Auto, Light, and Dark immediately; Auto follows
the simulator system appearance. the simulator system appearance.
- [ ] Icon and appearance settings remain selected after relaunch. - [ ] Icon and appearance settings remain selected after relaunch.

View File

@@ -110,7 +110,10 @@ pub fn normalize_primary_destinations(destinations: &mut Vec<crate::PrimaryDesti
let original = destinations.clone(); let original = destinations.clone();
let mut unique = Vec::with_capacity(destinations.len().min(4)); let mut unique = Vec::with_capacity(destinations.len().min(4));
for destination in destinations.drain(..) { for destination in destinations.drain(..) {
if !unique.contains(&destination) && unique.len() < 4 { if destination != crate::PrimaryDestination::ServerActivity
&& !unique.contains(&destination)
&& unique.len() < 4
{
unique.push(destination); unique.push(destination);
} }
} }
@@ -256,6 +259,7 @@ mod tests {
); );
let mut destinations = vec![ let mut destinations = vec![
crate::PrimaryDestination::ServerActivity,
crate::PrimaryDestination::Actions, crate::PrimaryDestination::Actions,
crate::PrimaryDestination::Actions, crate::PrimaryDestination::Actions,
crate::PrimaryDestination::Issues, crate::PrimaryDestination::Issues,
@@ -273,5 +277,6 @@ mod tests {
crate::PrimaryDestination::PullRequests, crate::PrimaryDestination::PullRequests,
] ]
); );
assert!(!destinations.contains(&crate::PrimaryDestination::ServerActivity));
} }
} }

View File

@@ -267,7 +267,7 @@ final class AppContext {
extension PrimaryDestination: CaseIterable { extension PrimaryDestination: CaseIterable {
public static var allCases: [PrimaryDestination] { public static var allCases: [PrimaryDestination] {
[.issues, .repositories, .pullRequests, .milestones, .actions, .serverActivity] [.issues, .repositories, .pullRequests, .milestones, .actions]
} }
var title: String { var title: String {

View File

@@ -27,12 +27,12 @@ final class NavigationSettingsViewController: UITableViewController {
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
section == 0 ? "Shown After Home" : "Available on Home" section == 0 ? "Shown After Home" : "Available Destinations"
} }
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
section == 0 section == 0
? "Choose up to four destinations. Tap Edit to reorder them. Home is always first." ? "Tap Edit to add, remove, or reorder up to four destinations. Home is always first."
: "Destinations not in the tab bar remain available as buttons on Home." : "Destinations not in the tab bar remain available as buttons on Home."
} }
@@ -49,19 +49,18 @@ final class NavigationSettingsViewController: UITableViewController {
if indexPath.section == 1 && selected.count == 4 { if indexPath.section == 1 && selected.count == 4 {
content.textProperties.color = .secondaryLabel content.textProperties.color = .secondaryLabel
content.imageProperties.tintColor = .tertiaryLabel content.imageProperties.tintColor = .tertiaryLabel
cell.selectionStyle = .none
} else if indexPath.section == 1 {
let add = UIImageView(image: UIImage(systemName: "plus.circle.fill"))
add.tintColor = .tintColor
cell.accessoryView = add
cell.accessibilityHint = "Adds this destination to the tab bar"
} }
cell.selectionStyle = .none
cell.contentConfiguration = content cell.contentConfiguration = content
return cell return cell
} }
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
tableView.isEditing && (indexPath.section == 0 || selected.count < 4)
}
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
indexPath.section == 0 tableView.isEditing && indexPath.section == 0
} }
override func tableView( override func tableView(
@@ -93,7 +92,8 @@ final class NavigationSettingsViewController: UITableViewController {
_ tableView: UITableView, _ tableView: UITableView,
editingStyleForRowAt indexPath: IndexPath editingStyleForRowAt indexPath: IndexPath
) -> UITableViewCell.EditingStyle { ) -> UITableViewCell.EditingStyle {
indexPath.section == 0 ? .delete : .none if indexPath.section == 0 { return .delete }
return selected.count < 4 ? .insert : .none
} }
override func tableView( override func tableView(
@@ -101,18 +101,20 @@ final class NavigationSettingsViewController: UITableViewController {
commit editingStyle: UITableViewCell.EditingStyle, commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath forRowAt indexPath: IndexPath
) { ) {
guard editingStyle == .delete, indexPath.section == 0 else { return } switch (editingStyle, indexPath.section) {
selected.remove(at: indexPath.row) case (.delete, 0):
selected.remove(at: indexPath.row)
case (.insert, 1) where selected.count < 4:
selected.append(available[indexPath.row])
default:
return
}
save() save()
tableView.reloadData() tableView.reloadData()
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true) tableView.deselectRow(at: indexPath, animated: true)
guard indexPath.section == 1, selected.count < 4 else { return }
selected.append(available[indexPath.row])
save()
tableView.reloadData()
} }
private func save() { private func save() {