diff options
| author | Christian Cleberg <[email protected]> | 2026-03-19 01:17:49 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-03-19 01:17:49 -0500 |
| commit | 441b69afae30ee6b662be38004fd7b5de1e47302 (patch) | |
| tree | 610827d41c7e7157290727b69b320f8dce95f08f /Hutch/App | |
| parent | ffc0ef0988b5e7d01951c23af85a1cf026b3da9f (diff) | |
| download | hutch-441b69afae30ee6b662be38004fd7b5de1e47302.tar.gz hutch-441b69afae30ee6b662be38004fd7b5de1e47302.tar.bz2 hutch-441b69afae30ee6b662be38004fd7b5de1e47302.zip | |
feat: add support for reading inbox messages (emails) and replying in-app
Diffstat (limited to 'Hutch/App')
| -rw-r--r-- | Hutch/App/AppState.swift | 14 | ||||
| -rw-r--r-- | Hutch/App/RootView.swift | 42 |
2 files changed, 36 insertions, 20 deletions
diff --git a/Hutch/App/AppState.swift b/Hutch/App/AppState.swift index 624c78d..7492fa6 100644 --- a/Hutch/App/AppState.swift +++ b/Hutch/App/AppState.swift @@ -7,6 +7,15 @@ import WebKit @MainActor final class AppState { + enum Tab: Hashable { + case home + case inbox + case repositories + case builds + case tickets + case settings + } + enum AuthPhase { /// App just launched, checking for an existing token. case launching @@ -25,6 +34,8 @@ final class AppState { authPhase == .authenticated && currentUser != nil } + var selectedTab: Tab = .home + // MARK: - Current user (populated after successful validation) private(set) var currentUser: User? @@ -94,6 +105,7 @@ final class AppState { await clearWebData() clearWebContentRenderCaches() authPhase = .unauthenticated + selectedTab = .home } func resetAppData() async { @@ -108,6 +120,7 @@ final class AppState { clearWebContentRenderCaches() authPhase = .unauthenticated + selectedTab = .home } // MARK: - Deep link resolution @@ -208,6 +221,7 @@ final class AppState { client.responseCache.clear() currentUser = nil pendingDeepLink = nil + selectedTab = .home } private func clearWebData() async { diff --git a/Hutch/App/RootView.swift b/Hutch/App/RootView.swift index 79411ed..36cbe5c 100644 --- a/Hutch/App/RootView.swift +++ b/Hutch/App/RootView.swift @@ -4,17 +4,8 @@ import SwiftUI /// full-screen sheet for token entry on first launch. struct RootView: View { @Environment(AppState.self) private var appState - - enum Tab: Hashable { - case home - case repositories - case builds - case tickets - case settings - } - - @State private var selectedTab: Tab = .home @State private var homePath = NavigationPath() + @State private var inboxPath = NavigationPath() @State private var repoPath = NavigationPath() @State private var buildsPath = NavigationPath() @State private var ticketsPath = NavigationPath() @@ -48,19 +39,29 @@ struct RootView: View { // MARK: - Tab View private var tabContent: some View { - TabView(selection: $selectedTab) { + @Bindable var appState = appState + + return TabView(selection: $appState.selectedTab) { NavigationStack(path: $homePath) { HomeView() } - .tag(Tab.home) + .tag(AppState.Tab.home) .tabItem { Label("Home", systemImage: "house") } + NavigationStack(path: $inboxPath) { + InboxView() + } + .tag(AppState.Tab.inbox) + .tabItem { + Label("Inbox", systemImage: "tray") + } + NavigationStack(path: $repoPath) { RepositoryListView() } - .tag(Tab.repositories) + .tag(AppState.Tab.repositories) .tabItem { Label("Repositories", systemImage: "book.closed") } @@ -73,7 +74,7 @@ struct RootView: View { BuildDetailView(jobId: jobId) } } - .tag(Tab.builds) + .tag(AppState.Tab.builds) .tabItem { Label("Builds", systemImage: "hammer") } @@ -85,13 +86,13 @@ struct RootView: View { TicketDetailView(ownerUsername: target.ownerUsername, trackerName: target.trackerName, trackerId: target.trackerId, trackerRid: target.trackerRid, ticketId: target.ticketId) } } - .tag(Tab.tickets) + .tag(AppState.Tab.tickets) .tabItem { Label("Tickets", systemImage: "ticket") } SettingsView() - .tag(Tab.settings) + .tag(AppState.Tab.settings) .tabItem { Label("Settings", systemImage: "gear") } @@ -117,10 +118,11 @@ struct RootView: View { break case .unauthenticated: homePath = NavigationPath() + inboxPath = NavigationPath() repoPath = NavigationPath() buildsPath = NavigationPath() ticketsPath = NavigationPath() - selectedTab = .home + appState.selectedTab = .home isResolvingDeepLink = false case .authenticated: consumePendingDeepLinkIfPossible(appState.pendingDeepLink) @@ -143,7 +145,7 @@ struct RootView: View { case .build(let jobId): // Reset the builds navigation and push the detail buildsPath = NavigationPath() - selectedTab = .builds + appState.selectedTab = .builds // Defer the push slightly so the tab switch takes effect Task { @MainActor in try? await Task.sleep(for: .milliseconds(100)) @@ -162,7 +164,7 @@ struct RootView: View { do { let summary = try await appState.resolveRepository(owner: owner, name: repo) repoPath = NavigationPath() - selectedTab = .repositories + appState.selectedTab = .repositories try? await Task.sleep(for: .milliseconds(100)) repoPath.append(summary) } catch { @@ -178,7 +180,7 @@ struct RootView: View { do { let trackerSummary = try await appState.resolveTracker(owner: owner, name: tracker) ticketsPath = NavigationPath() - selectedTab = .tickets + appState.selectedTab = .tickets try? await Task.sleep(for: .milliseconds(100)) ticketsPath.append(trackerSummary) try? await Task.sleep(for: .milliseconds(100)) |
