Commit 5fc8e80dbe

5fc8e80dbe926c2f5a6d38a3d4763be542f8d2a4

parent: 4f441dbc04

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-20 23:07 UTC

push: route a tap to the issue, merge request or build

A tap for another account switches to it first; the rebuild that
causes is what delivers the route, since the pending target outlives
the subtree.

Ref krz/gitbay#89

Layout: unified · split

gitbay/ContentView.swift +44 −7
@@ -3,35 +3,41 @@ import SwiftUI
33struct ContentView: View {
44
55 @Environment(SessionStore.self) private var session
6 @Environment(PushRouter.self) private var router
7
8 enum Tabs { case dashboard, feed, repositories, explore, profile }
9
10 @State private var selectedTab = Tabs.dashboard
11 @State private var dashboardPath = NavigationPath()
612
713 var body: some View {
814 if let client = session.client, let account = session.current {
9 TabView {
10 Tab("Dashboard", systemImage: "square.grid.2x2") {
11 NavigationStack {
15 TabView(selection: $selectedTab) {
16 Tab("Dashboard", systemImage: "square.grid.2x2", value: Tabs.dashboard) {
17 NavigationStack(path: $dashboardPath) {
1218 DashboardView(client: client)
1319 .navigationDestinations(client: client)
1420 }
1521 }
16 Tab("Feed", systemImage: "bolt") {
22 Tab("Feed", systemImage: "bolt", value: Tabs.feed) {
1723 NavigationStack {
1824 FeedView(client: client)
1925 .navigationDestinations(client: client)
2026 }
2127 }
22 Tab("Repositories", systemImage: "books.vertical") {
28 Tab("Repositories", systemImage: "books.vertical", value: Tabs.repositories) {
2329 NavigationStack {
2430 RepoListView(client: client)
2531 .navigationDestinations(client: client)
2632 }
2733 }
28 Tab("Explore", systemImage: "safari") {
34 Tab("Explore", systemImage: "safari", value: Tabs.explore) {
2935 NavigationStack {
3036 ExploreView(client: client)
3137 .navigationDestinations(client: client)
3238 }
3339 }
34 Tab("My Profile", systemImage: "person.crop.circle") {
40 Tab("My Profile", systemImage: "person.crop.circle", value: Tabs.profile) {
3541 NavigationStack {
3642 ProfileView(client: client, name: account.username,
3743 title: "My Profile")
@@ -41,6 +47,7 @@ struct ContentView: View {
4147 }
4248 }
4349 .id(account.id) // fresh screens on account switch
50 .task(id: router.pending) { await routePendingNotification() }
4451 } else {
4552 NavigationStack {
4653 SignInView()
@@ -48,6 +55,36 @@ struct ContentView: View {
4855 }
4956 }
5057
58 /// Routes a tapped notification, switching accounts first when it
59 /// belongs to another one.
60 ///
61 /// A switch changes `account.id`, which rebuilds this whole view.
62 /// That is why the target is left pending across it: the rebuilt
63 /// tree runs this again and finds the account already active, so the
64 /// second pass does the navigating. The drain is the mechanism, not
65 /// a workaround for it.
66 private func routePendingNotification() async {
67 guard let target = router.pending else { return }
68 if session.current?.id != target.accountID {
69 guard let account = session.accounts.first(where: { $0.id == target.accountID }) else {
70 router.pending = nil // signed out of it since the tap
71 return
72 }
73 _ = session.activate(account)
74 return // the rebuilt tree drains it
75 }
76 selectedTab = .dashboard
77 switch target.destination {
78 case .issue(let repo, let number):
79 dashboardPath.append(IssueRoute.issue(repo: repo, number: number))
80 case .mr(let repo, let number):
81 dashboardPath.append(MRRoute.mr(repo: repo, number: number))
82 case .build(let repo, let number):
83 dashboardPath.append(BuildRoute.detail(repo: repo, number: number))
84 }
85 router.pending = nil
86 }
87
5188}
5289
5390/// Every value-routed screen, attachable to any stack root.