Commit c108ce3805
Unsigned
Layout: unified · split
gitbay/ContentView.swift +15 −3
| @@ -12,6 +12,9 @@ struct ContentView: View { | ||
| 12 | 12 | @State private var selectedTab = Tabs.dashboard |
| 13 | 13 | @State private var dashboardPath = NavigationPath() |
| 14 | 14 | @State private var repoPath = NavigationPath() |
| 15 | @State private var feedPath = NavigationPath() | |
| 16 | @State private var explorePath = NavigationPath() | |
| 17 | @State private var profilePath = NavigationPath() | |
| 15 | 18 | @State private var unread = 0 |
| 16 | 19 | |
| 17 | 20 | /// whoami's admin flag, read on the My Profile tab, decides whether |
| @@ -40,14 +43,16 @@ struct ContentView: View { | ||
| 40 | 43 | DashboardView(client: client, unread: $unread) |
| 41 | 44 | .navigationDestinations(client: client) |
| 42 | 45 | } |
| 46 | .pushesRoutes(into: $dashboardPath) | |
| 43 | 47 | } |
| 44 | 48 | } |
| 45 | 49 | .badge(unread) |
| 46 | 50 | Tab("Feed", systemImage: "bolt", value: Tabs.feed) { |
| 47 | NavigationStack { | |
| 51 | NavigationStack(path: $feedPath) { | |
| 48 | 52 | FeedView(client: client) |
| 49 | 53 | .navigationDestinations(client: client) |
| 50 | 54 | } |
| 55 | .pushesRoutes(into: $feedPath) | |
| 51 | 56 | } |
| 52 | 57 | Tab("Repositories", systemImage: "books.vertical", value: Tabs.repositories) { |
| 53 | 58 | if sizeClass == .regular { |
| @@ -59,16 +64,18 @@ struct ContentView: View { | ||
| 59 | 64 | RepoListView(client: client) |
| 60 | 65 | .navigationDestinations(client: client) |
| 61 | 66 | } |
| 67 | .pushesRoutes(into: $repoPath) | |
| 62 | 68 | } |
| 63 | 69 | } |
| 64 | 70 | Tab("Explore", systemImage: "safari", value: Tabs.explore) { |
| 65 | NavigationStack { | |
| 71 | NavigationStack(path: $explorePath) { | |
| 66 | 72 | ExploreView(client: client) |
| 67 | 73 | .navigationDestinations(client: client) |
| 68 | 74 | } |
| 75 | .pushesRoutes(into: $explorePath) | |
| 69 | 76 | } |
| 70 | 77 | Tab("My Profile", systemImage: "person.crop.circle", value: Tabs.profile) { |
| 71 | NavigationStack { | |
| 78 | NavigationStack(path: $profilePath) { | |
| 72 | 79 | ProfileView(client: client, name: account.username, |
| 73 | 80 | title: "My Profile") |
| 74 | 81 | .toolbar { AccountMenu(isAdmin: isAdmin) } |
| @@ -78,6 +85,7 @@ struct ContentView: View { | ||
| 78 | 85 | } |
| 79 | 86 | .navigationDestinations(client: client) |
| 80 | 87 | } |
| 88 | .pushesRoutes(into: $profilePath) | |
| 81 | 89 | } |
| 82 | 90 | } |
| 83 | 91 | .id(account.id) // a fresh tab bar and stacks per account |
| @@ -123,6 +131,9 @@ struct ContentView: View { | ||
| 123 | 131 | if pathAccount != account.id { |
| 124 | 132 | dashboardPath = NavigationPath() |
| 125 | 133 | repoPath = NavigationPath() |
| 134 | feedPath = NavigationPath() | |
| 135 | explorePath = NavigationPath() | |
| 136 | profilePath = NavigationPath() | |
| 126 | 137 | selectedTab = .dashboard |
| 127 | 138 | pathAccount = account.id |
| 128 | 139 | } |
| @@ -229,6 +240,7 @@ private struct SplitStack<Sidebar: View>: View { | ||
| 229 | 240 | } |
| 230 | 241 | .navigationDestinations(client: client) |
| 231 | 242 | } |
| 243 | .pushesRoutes(into: $path) | |
| 232 | 244 | } |
| 233 | 245 | } |
| 234 | 246 | } |
gitbay/Views/Account/NotificationsView.swift +8 −6
| @@ -17,13 +17,15 @@ struct NotificationsView: View { | ||
| 17 | 17 | |
| 18 | 18 | var body: some View { |
| 19 | 19 | List { |
| 20 | Picker("Filter", selection: Bindable(model).showAll) { | |
| 21 | Text("Unread").tag(false) | |
| 22 | Text("All").tag(true) | |
| 20 | Section { | |
| 21 | Picker("Filter", selection: Bindable(model).showAll) { | |
| 22 | Text("Unread").tag(false) | |
| 23 | Text("All").tag(true) | |
| 24 | } | |
| 25 | .pickerStyle(.segmented) | |
| 26 | .listRowBackground(Color.clear) | |
| 27 | .listRowInsets(EdgeInsets()) | |
| 23 | 28 | } |
| 24 | .pickerStyle(.segmented) | |
| 25 | .listRowBackground(Color.clear) | |
| 26 | .listRowInsets(EdgeInsets()) | |
| 27 | 29 | |
| 28 | 30 | if let actionError = model.actionError { |
| 29 | 31 | Section { GBNotice(actionError) } |
gitbay/Views/Admin/AdminUsersView.swift +9 −7
| @@ -20,15 +20,17 @@ struct AdminUsersView: View { | ||
| 20 | 20 | |
| 21 | 21 | var body: some View { |
| 22 | 22 | List { |
| 23 | Picker("State", selection: Bindable(model).filter) { | |
| 24 | ForEach(AdminUserFilter.allCases) { filter in | |
| 25 | Text(filter.label).tag(filter) | |
| 23 | Section { | |
| 24 | Picker("State", selection: Bindable(model).filter) { | |
| 25 | ForEach(AdminUserFilter.allCases) { filter in | |
| 26 | Text(filter.label).tag(filter) | |
| 27 | } | |
| 26 | 28 | } |
| 29 | .pickerStyle(.segmented) | |
| 30 | .listRowBackground(Color.clear) | |
| 31 | .listRowInsets(EdgeInsets()) | |
| 32 | .accessibilityIdentifier("admin-user-filter") | |
| 27 | 33 | } |
| 28 | .pickerStyle(.segmented) | |
| 29 | .listRowBackground(Color.clear) | |
| 30 | .listRowInsets(EdgeInsets()) | |
| 31 | .accessibilityIdentifier("admin-user-filter") | |
| 32 | 34 | |
| 33 | 35 | if let error = model.actionError { |
| 34 | 36 | Section { |
gitbay/Views/Builds/BuildDetailView.swift +24 −14
| @@ -29,6 +29,9 @@ struct BuildDetailView: View { | ||
| 29 | 29 | .padding(.vertical, 8) |
| 30 | 30 | } |
| 31 | 31 | header(detail.build) |
| 32 | if !detail.log.isEmpty { | |
| 33 | jumpBar(failed: detail.lines.failed != nil) | |
| 34 | } | |
| 32 | 35 | Divider() |
| 33 | 36 | log(detail) |
| 34 | 37 | } |
| @@ -39,20 +42,6 @@ struct BuildDetailView: View { | ||
| 39 | 42 | .navigationBarTitleDisplayMode(.inline) |
| 40 | 43 | .searchable(text: $query, prompt: "Filter log lines") |
| 41 | 44 | .toolbar { |
| 42 | ToolbarItemGroup(placement: .topBarTrailing) { | |
| 43 | if let detail = model.state.value, !detail.log.isEmpty { | |
| 44 | if detail.lines.failed != nil { | |
| 45 | Button("Jump to failure", systemImage: "exclamationmark.triangle") { | |
| 46 | jump = .failure | |
| 47 | } | |
| 48 | .accessibilityIdentifier("build-jump-failure") | |
| 49 | } | |
| 50 | Button("Jump to end", systemImage: "arrow.down.to.line") { | |
| 51 | jump = .end | |
| 52 | } | |
| 53 | .accessibilityIdentifier("build-jump-end") | |
| 54 | } | |
| 55 | } | |
| 56 | 45 | ToolbarItem(placement: .topBarTrailing) { |
| 57 | 46 | if model.isCancellable { |
| 58 | 47 | if model.working { |
| @@ -89,6 +78,27 @@ struct BuildDetailView: View { | ||
| 89 | 78 | } |
| 90 | 79 | } |
| 91 | 80 | |
| 81 | private func jumpBar(failed: Bool) -> some View { | |
| 82 | HStack(spacing: 20) { | |
| 83 | if failed { | |
| 84 | Button("Jump to failure", systemImage: "exclamationmark.triangle") { | |
| 85 | jump = .failure | |
| 86 | } | |
| 87 | .accessibilityIdentifier("build-jump-failure") | |
| 88 | } | |
| 89 | Button("Jump to end", systemImage: "arrow.down.to.line") { | |
| 90 | jump = .end | |
| 91 | } | |
| 92 | .accessibilityIdentifier("build-jump-end") | |
| 93 | Spacer() | |
| 94 | } | |
| 95 | .font(.gbSans(.caption)) | |
| 96 | .foregroundStyle(Color.gbAccent) | |
| 97 | .buttonStyle(.borderless) | |
| 98 | .padding(.horizontal, 16) | |
| 99 | .padding(.bottom, 10) | |
| 100 | } | |
| 101 | ||
| 92 | 102 | private func header(_ build: Build) -> some View { |
| 93 | 103 | VStack(alignment: .leading, spacing: 6) { |
| 94 | 104 | HStack(spacing: 8) { |
gitbay/Views/Discovery/ExploreView.swift +8 −6
| @@ -42,14 +42,16 @@ struct ExploreView: View { | ||
| 42 | 42 | var body: some View { |
| 43 | 43 | List { |
| 44 | 44 | if search.isSearching { |
| 45 | Picker("Kind", selection: Bindable(search).kind) { | |
| 46 | ForEach(SearchKind.allCases) { kind in | |
| 47 | Text(kind.label).tag(kind) | |
| 45 | Section { | |
| 46 | Picker("Kind", selection: Bindable(search).kind) { | |
| 47 | ForEach(SearchKind.allCases) { kind in | |
| 48 | Text(kind.label).tag(kind) | |
| 49 | } | |
| 48 | 50 | } |
| 51 | .pickerStyle(.segmented) | |
| 52 | .listRowBackground(Color.clear) | |
| 53 | .listRowInsets(EdgeInsets()) | |
| 49 | 54 | } |
| 50 | .pickerStyle(.segmented) | |
| 51 | .listRowBackground(Color.clear) | |
| 52 | .listRowInsets(EdgeInsets()) | |
| 53 | 55 | |
| 54 | 56 | ForEach(search.state.value ?? []) { result in |
| 55 | 57 | searchRow(for: result) |
gitbay/Views/Issues/IssueListView.swift +10 −11
| @@ -9,7 +9,7 @@ struct IssueListView: View { | ||
| 9 | 9 | @State private var draftTitle = "" |
| 10 | 10 | @State private var draftBody = "" |
| 11 | 11 | @State private var draftFormat = "md" |
| 12 | @State private var createdIssue: Int64? | |
| 12 | @Environment(\.pushRoute) private var pushRoute | |
| 13 | 13 | |
| 14 | 14 | private let client: GitbayClient |
| 15 | 15 | |
| @@ -21,14 +21,16 @@ struct IssueListView: View { | ||
| 21 | 21 | |
| 22 | 22 | var body: some View { |
| 23 | 23 | List { |
| 24 | Picker("State", selection: Bindable(model).filter.state) { | |
| 25 | ForEach(IssueFilter.State.allCases) { filter in | |
| 26 | Text(filter.label).tag(filter) | |
| 24 | Section { | |
| 25 | Picker("State", selection: Bindable(model).filter.state) { | |
| 26 | ForEach(IssueFilter.State.allCases) { filter in | |
| 27 | Text(filter.label).tag(filter) | |
| 28 | } | |
| 27 | 29 | } |
| 30 | .pickerStyle(.segmented) | |
| 31 | .listRowBackground(Color.clear) | |
| 32 | .listRowInsets(EdgeInsets()) | |
| 28 | 33 | } |
| 29 | .pickerStyle(.segmented) | |
| 30 | .listRowBackground(Color.clear) | |
| 31 | .listRowInsets(EdgeInsets()) | |
| 32 | 34 | |
| 33 | 35 | ForEach(model.state.value ?? []) { issue in |
| 34 | 36 | NavigationLink(value: IssueRoute.issue(repo: model.repoPath, number: issue.number)) { |
| @@ -101,15 +103,12 @@ struct IssueListView: View { | ||
| 101 | 103 | draftBody = "" |
| 102 | 104 | draftFormat = "md" |
| 103 | 105 | composing = false |
| 104 | createdIssue = number | |
| 106 | pushRoute(IssueRoute.issue(repo: model.repoPath, number: number)) | |
| 105 | 107 | await model.load() |
| 106 | 108 | } |
| 107 | 109 | } |
| 108 | 110 | } |
| 109 | 111 | } |
| 110 | .navigationDestination(item: $createdIssue) { number in | |
| 111 | IssueView(client: client, repo: model.repoPath, number: number) | |
| 112 | } | |
| 113 | 112 | .task { await model.loadIfNeeded() } |
| 114 | 113 | .task { await createModel.loadTemplates() } |
| 115 | 114 | .refreshable { await model.load() } |
gitbay/Views/Issues/MilestoneListView.swift +8 −6
| @@ -16,14 +16,16 @@ struct MilestoneListView: View { | ||
| 16 | 16 | |
| 17 | 17 | var body: some View { |
| 18 | 18 | List { |
| 19 | Picker("State", selection: Bindable(model).filter) { | |
| 20 | ForEach(MilestoneListViewModel.StateFilter.allCases) { filter in | |
| 21 | Text(filter.rawValue.capitalized).tag(filter) | |
| 19 | Section { | |
| 20 | Picker("State", selection: Bindable(model).filter) { | |
| 21 | ForEach(MilestoneListViewModel.StateFilter.allCases) { filter in | |
| 22 | Text(filter.rawValue.capitalized).tag(filter) | |
| 23 | } | |
| 22 | 24 | } |
| 25 | .pickerStyle(.segmented) | |
| 26 | .listRowBackground(Color.clear) | |
| 27 | .listRowInsets(EdgeInsets()) | |
| 23 | 28 | } |
| 24 | .pickerStyle(.segmented) | |
| 25 | .listRowBackground(Color.clear) | |
| 26 | .listRowInsets(EdgeInsets()) | |
| 27 | 29 | |
| 28 | 30 | if let error = model.actionError { |
| 29 | 31 | Section { |
gitbay/Views/MRs/MRListView.swift +8 −6
| @@ -14,14 +14,16 @@ struct MRListView: View { | ||
| 14 | 14 | |
| 15 | 15 | var body: some View { |
| 16 | 16 | List { |
| 17 | Picker("State", selection: Bindable(model).filter.state) { | |
| 18 | ForEach(MRFilter.State.allCases) { filter in | |
| 19 | Text(filter.label).tag(filter) | |
| 17 | Section { | |
| 18 | Picker("State", selection: Bindable(model).filter.state) { | |
| 19 | ForEach(MRFilter.State.allCases) { filter in | |
| 20 | Text(filter.label).tag(filter) | |
| 21 | } | |
| 20 | 22 | } |
| 23 | .pickerStyle(.segmented) | |
| 24 | .listRowBackground(Color.clear) | |
| 25 | .listRowInsets(EdgeInsets()) | |
| 21 | 26 | } |
| 22 | .pickerStyle(.segmented) | |
| 23 | .listRowBackground(Color.clear) | |
| 24 | .listRowInsets(EdgeInsets()) | |
| 25 | 27 | |
| 26 | 28 | ForEach(model.state.value ?? []) { mr in |
| 27 | 29 | NavigationLink(value: MRRoute.mr(repo: model.repoPath, number: mr.number)) { |
gitbay/Views/Repos/RepoView.swift +24 −30
| @@ -19,10 +19,9 @@ struct RepoView: View { | ||
| 19 | 19 | @State private var confirmingArchive = false |
| 20 | 20 | @State private var confirmingFork = false |
| 21 | 21 | @State private var forkName = "" |
| 22 | @State private var forkDestination: RepoRoute? | |
| 23 | 22 | @Environment(\.dynamicTypeSize) private var typeSize |
| 24 | 23 | @ScaledMetric(relativeTo: .title3) private var statValueHeight: CGFloat = 26 |
| 25 | @State private var destination: Destination? | |
| 24 | @Environment(\.pushRoute) private var pushRoute | |
| 26 | 25 | |
| 27 | 26 | /// Pushed from buttons rather than NavigationLinks: several share a |
| 28 | 27 | /// list row, and a link there claims the whole row and adds a chevron. |
| @@ -31,6 +30,24 @@ struct RepoView: View { | ||
| 31 | 30 | case history, refs, milestones, labels, releases, wiki, grep |
| 32 | 31 | } |
| 33 | 32 | |
| 33 | private func open(_ target: Destination) { | |
| 34 | switch target { | |
| 35 | case .files: pushRoute(RepoRoute.tree(repo: path, directory: "", ref: nil)) | |
| 36 | case .mergeRequests: pushRoute(MRRoute.list(repo: path)) | |
| 37 | case .issues: pushRoute(IssueRoute.list(repo: path)) | |
| 38 | case .builds: pushRoute(BuildRoute.list(repo: path)) | |
| 39 | case .owner: pushRoute(RepoRoute.profile(owner)) | |
| 40 | case .settings: pushRoute(RepoRoute.settings(repo: path)) | |
| 41 | case .history: pushRoute(RepoRoute.log(repo: path, ref: nil, path: nil)) | |
| 42 | case .refs: pushRoute(RepoRoute.refs(repo: path)) | |
| 43 | case .milestones: pushRoute(RepoRoute.milestones(repo: path)) | |
| 44 | case .labels: pushRoute(RepoRoute.labels(repo: path)) | |
| 45 | case .releases: pushRoute(ReleaseRoute.list(repo: path)) | |
| 46 | case .wiki: pushRoute(RepoRoute.wiki(repo: path)) | |
| 47 | case .grep: pushRoute(RepoRoute.grep(repo: path)) | |
| 48 | } | |
| 49 | } | |
| 50 | ||
| 34 | 51 | var body: some View { |
| 35 | 52 | List { |
| 36 | 53 | if let detail = model.state.value { |
| @@ -80,28 +97,6 @@ struct RepoView: View { | ||
| 80 | 97 | .toolbar { toolbar } |
| 81 | 98 | .task { await model.load() } |
| 82 | 99 | .refreshable { await model.load() } |
| 83 | .navigationDestination(item: $forkDestination) { route in | |
| 84 | if case .repo(let forkedPath) = route { | |
| 85 | RepoView(client: client, path: forkedPath) | |
| 86 | } | |
| 87 | } | |
| 88 | .navigationDestination(item: $destination) { destination in | |
| 89 | switch destination { | |
| 90 | case .files: TreeView(client: client, repo: path, directory: "", ref: nil) | |
| 91 | case .mergeRequests: MRListView(client: client, repo: path) | |
| 92 | case .issues: IssueListView(client: client, repo: path) | |
| 93 | case .builds: BuildListView(client: client, repo: path) | |
| 94 | case .owner: ProfileView(client: client, name: owner) | |
| 95 | case .settings: RepoSettingsView(client: client, repo: path) | |
| 96 | case .history: LogView(client: client, repo: path, ref: nil, path: nil) | |
| 97 | case .refs: RefsView(client: client, repo: path) | |
| 98 | case .milestones: MilestoneListView(client: client, repo: path) | |
| 99 | case .labels: LabelListView(client: client, repo: path) | |
| 100 | case .releases: ReleaseListView(client: client, repo: path) | |
| 101 | case .wiki: WikiView(client: client, repo: path) | |
| 102 | case .grep: GrepView(client: client, repo: path) | |
| 103 | } | |
| 104 | } | |
| 105 | 100 | .alert("Fork \(path)", isPresented: $confirmingFork) { |
| 106 | 101 | TextField("New name (optional)", text: $forkName) |
| 107 | 102 | .autocorrectionDisabled() |
| @@ -109,7 +104,7 @@ struct RepoView: View { | ||
| 109 | 104 | Button("Fork") { |
| 110 | 105 | Task { |
| 111 | 106 | if let result = await actionsModel.fork(named: forkName) { |
| 112 | forkDestination = .repo(result.path) | |
| 107 | pushRoute(RepoRoute.repo(result.path)) | |
| 113 | 108 | } |
| 114 | 109 | } |
| 115 | 110 | } |
| @@ -208,7 +203,7 @@ struct RepoView: View { | ||
| 208 | 203 | } |
| 209 | 204 | Divider() |
| 210 | 205 | Button { |
| 211 | destination = .settings | |
| 206 | open(.settings) | |
| 212 | 207 | } label: { |
| 213 | 208 | Label("Settings", systemImage: "gearshape") |
| 214 | 209 | } |
| @@ -239,7 +234,7 @@ struct RepoView: View { | ||
| 239 | 234 | VStack(alignment: .leading, spacing: 8) { |
| 240 | 235 | HStack(spacing: 6) { |
| 241 | 236 | HStack(spacing: 0) { |
| 242 | Button(owner) { destination = .owner } | |
| 237 | Button(owner) { open(.owner) } | |
| 243 | 238 | .buttonStyle(.borderless) |
| 244 | 239 | .accessibilityIdentifier("repo-owner-link") |
| 245 | 240 | Text("/" + (detail.path.split(separator: "/").last.map(String.init) ?? "")) |
| @@ -348,7 +343,7 @@ struct RepoView: View { | ||
| 348 | 343 | _ title: String, label: String, value: String? = nil, id: String, |
| 349 | 344 | to target: Destination, first: Bool, @ViewBuilder top: () -> some View |
| 350 | 345 | ) -> some View { |
| 351 | Button { destination = target } label: { | |
| 346 | Button { open(target) } label: { | |
| 352 | 347 | VStack(spacing: 2) { |
| 353 | 348 | top() |
| 354 | 349 | .font(.gbSans(.title3)) |
| @@ -409,7 +404,7 @@ struct RepoView: View { | ||
| 409 | 404 | } |
| 410 | 405 | |
| 411 | 406 | private func pill(_ title: String, systemImage: String, to target: Destination) -> some View { |
| 412 | Button { destination = target } label: { | |
| 407 | Button { open(target) } label: { | |
| 413 | 408 | Label { |
| 414 | 409 | Text(title).lineLimit(1).minimumScaleFactor(0.8) |
| 415 | 410 | } icon: { |
| @@ -419,7 +414,6 @@ struct RepoView: View { | ||
| 419 | 414 | .padding(.horizontal, 10) |
| 420 | 415 | .padding(.vertical, 8) |
| 421 | 416 | .frame(maxWidth: .infinity, alignment: .leading) |
| 422 | .overlay(RoundedRectangle(cornerRadius: 2).stroke(Color.gbLine, lineWidth: 0.5)) | |
| 423 | 417 | .contentShape(Rectangle()) |
| 424 | 418 | } |
| 425 | 419 | .buttonStyle(.plain) |
gitbay/Views/Shared/BodyReferences.swift +6 −15
| @@ -87,27 +87,18 @@ extension View { | ||
| 87 | 87 | |
| 88 | 88 | private struct ReferenceNavigation: ViewModifier { |
| 89 | 89 | |
| 90 | @Environment(SessionStore.self) private var session | |
| 91 | @State private var target: NotificationDestination? | |
| 90 | @Environment(\.pushRoute) private var pushRoute | |
| 92 | 91 | |
| 93 | 92 | func body(content: Content) -> some View { |
| 94 | 93 | content |
| 95 | 94 | .environment(\.openURL, OpenURLAction { url in |
| 96 | 95 | guard let destination = referenceDestination(url) else { return .systemAction } |
| 97 | target = destination | |
| 96 | switch destination { | |
| 97 | case .issue(let repo, let number): pushRoute(IssueRoute.issue(repo: repo, number: number)) | |
| 98 | case .mr(let repo, let number): pushRoute(MRRoute.mr(repo: repo, number: number)) | |
| 99 | case .build(let repo, let number): pushRoute(BuildRoute.detail(repo: repo, number: number)) | |
| 100 | } | |
| 98 | 101 | return .handled |
| 99 | 102 | }) |
| 100 | .navigationDestination(item: $target) { destination in | |
| 101 | if let client = session.client { | |
| 102 | switch destination { | |
| 103 | case .issue(let repo, let number): | |
| 104 | IssueView(client: client, repo: repo, number: number) | |
| 105 | case .mr(let repo, let number): | |
| 106 | MRView(client: client, repo: repo, number: number) | |
| 107 | case .build(let repo, let number): | |
| 108 | BuildDetailView(client: client, repo: repo, number: number) | |
| 109 | } | |
| 110 | } | |
| 111 | } | |
| 112 | 103 | } |
| 113 | 104 | } |
gitbay/Views/Shared/SidebarRouting.swift +12
| @@ -4,6 +4,18 @@ extension EnvironmentValues { | ||
| 4 | 4 | /// Set on a list shown as a `NavigationSplitView` sidebar: what it |
| 5 | 5 | /// selects opens in the detail column's stack. Nil in a plain stack. |
| 6 | 6 | @Entry var sidebarSelection: Binding<AnyHashable?>? |
| 7 | ||
| 8 | /// Appends a route to the enclosing stack's path. Screens that push | |
| 9 | /// from a button use it instead of `navigationDestination(item:)`, | |
| 10 | /// which puts any later value-link push underneath the pushed screen. | |
| 11 | @Entry var pushRoute: (any Hashable) -> Void = { _ in } | |
| 12 | } | |
| 13 | ||
| 14 | extension View { | |
| 15 | /// Lets screens in this stack push onto `path` with `pushRoute`. | |
| 16 | func pushesRoutes(into path: Binding<NavigationPath>) -> some View { | |
| 17 | environment(\.pushRoute) { path.wrappedValue.append($0) } | |
| 18 | } | |
| 7 | 19 | } |
| 8 | 20 | |
| 9 | 21 | /// A `List` whose rows select into `sidebarSelection` when there is one. |