Commit 5fc8e80dbe
Verified · cmc
Layout: unified · split
gitbay/ContentView.swift +44 −7
| @@ -3,35 +3,41 @@ import SwiftUI | |||
| 3 | struct ContentView: View { | 3 | struct ContentView: View { |
| 4 | 4 | ||
| 5 | @Environment(SessionStore.self) private var session | 5 | @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() | ||
| 6 | 12 | ||
| 7 | var body: some View { | 13 | var body: some View { |
| 8 | if let client = session.client, let account = session.current { | 14 | if let client = session.client, let account = session.current { |
| 9 | TabView { | 15 | TabView(selection: $selectedTab) { |
| 10 | Tab("Dashboard", systemImage: "square.grid.2x2") { | 16 | Tab("Dashboard", systemImage: "square.grid.2x2", value: Tabs.dashboard) { |
| 11 | NavigationStack { | 17 | NavigationStack(path: $dashboardPath) { |
| 12 | DashboardView(client: client) | 18 | DashboardView(client: client) |
| 13 | .navigationDestinations(client: client) | 19 | .navigationDestinations(client: client) |
| 14 | } | 20 | } |
| 15 | } | 21 | } |
| 16 | Tab("Feed", systemImage: "bolt") { | 22 | Tab("Feed", systemImage: "bolt", value: Tabs.feed) { |
| 17 | NavigationStack { | 23 | NavigationStack { |
| 18 | FeedView(client: client) | 24 | FeedView(client: client) |
| 19 | .navigationDestinations(client: client) | 25 | .navigationDestinations(client: client) |
| 20 | } | 26 | } |
| 21 | } | 27 | } |
| 22 | Tab("Repositories", systemImage: "books.vertical") { | 28 | Tab("Repositories", systemImage: "books.vertical", value: Tabs.repositories) { |
| 23 | NavigationStack { | 29 | NavigationStack { |
| 24 | RepoListView(client: client) | 30 | RepoListView(client: client) |
| 25 | .navigationDestinations(client: client) | 31 | .navigationDestinations(client: client) |
| 26 | } | 32 | } |
| 27 | } | 33 | } |
| 28 | Tab("Explore", systemImage: "safari") { | 34 | Tab("Explore", systemImage: "safari", value: Tabs.explore) { |
| 29 | NavigationStack { | 35 | NavigationStack { |
| 30 | ExploreView(client: client) | 36 | ExploreView(client: client) |
| 31 | .navigationDestinations(client: client) | 37 | .navigationDestinations(client: client) |
| 32 | } | 38 | } |
| 33 | } | 39 | } |
| 34 | Tab("My Profile", systemImage: "person.crop.circle") { | 40 | Tab("My Profile", systemImage: "person.crop.circle", value: Tabs.profile) { |
| 35 | NavigationStack { | 41 | NavigationStack { |
| 36 | ProfileView(client: client, name: account.username, | 42 | ProfileView(client: client, name: account.username, |
| 37 | title: "My Profile") | 43 | title: "My Profile") |
| @@ -41,6 +47,7 @@ struct ContentView: View { | |||
| 41 | } | 47 | } |
| 42 | } | 48 | } |
| 43 | .id(account.id) // fresh screens on account switch | 49 | .id(account.id) // fresh screens on account switch |
| 50 | .task(id: router.pending) { await routePendingNotification() } | ||
| 44 | } else { | 51 | } else { |
| 45 | NavigationStack { | 52 | NavigationStack { |
| 46 | SignInView() | 53 | SignInView() |
| @@ -48,6 +55,36 @@ struct ContentView: View { | |||
| 48 | } | 55 | } |
| 49 | } | 56 | } |
| 50 | 57 | ||
| 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 | |||
| 51 | } | 88 | } |
| 52 | 89 | ||
| 53 | /// Every value-routed screen, attachable to any stack root. | 90 | /// Every value-routed screen, attachable to any stack root. |