summaryrefslogtreecommitdiff
path: root/Hutch/App/RootView.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-03-19 15:17:38 -0500
committerChristian Cleberg <[email protected]>2026-03-19 15:17:38 -0500
commit6ba4e967d5dfb5d3c7bb97a0f2662f3180595563 (patch)
tree572bef546fcca19ad37bc1aa7cfb153eb2bf8eb7 /Hutch/App/RootView.swift
parent1e3c748119c6e9eec27f02146f17ea0302ff648a (diff)
downloadhutch-6ba4e967d5dfb5d3c7bb97a0f2662f3180595563.tar.gz
hutch-6ba4e967d5dfb5d3c7bb97a0f2662f3180595563.tar.bz2
hutch-6ba4e967d5dfb5d3c7bb97a0f2662f3180595563.zip
feat: implement support for projects, lists, and pastes
Diffstat (limited to 'Hutch/App/RootView.swift')
-rw-r--r--Hutch/App/RootView.swift110
1 files changed, 94 insertions, 16 deletions
diff --git a/Hutch/App/RootView.swift b/Hutch/App/RootView.swift
index 042494f..0885835 100644
--- a/Hutch/App/RootView.swift
+++ b/Hutch/App/RootView.swift
@@ -5,7 +5,7 @@ import SwiftUI
struct RootView: View {
@Environment(AppState.self) private var appState
@State private var homePath = NavigationPath()
- @State private var inboxPath = NavigationPath()
+ @State private var morePath = NavigationPath()
@State private var repoPath = NavigationPath()
@State private var buildsPath = NavigationPath()
@State private var ticketsPath = NavigationPath()
@@ -34,6 +34,9 @@ struct RootView: View {
.onChange(of: appState.authPhase) { _, newPhase in
handleAuthPhaseChange(newPhase)
}
+ .onChange(of: appState.pendingTabNavigation) { _, newValue in
+ consumePendingTabNavigationIfPossible(newValue)
+ }
}
// MARK: - Tab View
@@ -50,14 +53,6 @@ struct RootView: View {
Label("Home", systemImage: "house")
}
- NavigationStack(path: $inboxPath) {
- InboxView()
- }
- .tag(AppState.Tab.inbox)
- .tabItem {
- Label("Inbox", systemImage: "tray")
- }
-
NavigationStack(path: $repoPath) {
RepositoryListView()
}
@@ -78,12 +73,6 @@ struct RootView: View {
Label("Tickets", systemImage: "ticket")
}
- SettingsView()
- .tag(AppState.Tab.settings)
- .tabItem {
- Label("Settings", systemImage: "gear")
- }
-
NavigationStack(path: $buildsPath) {
BuildListView()
// Int destination used by deep links (hutch://builds/<id>).
@@ -96,6 +85,14 @@ struct RootView: View {
.tabItem {
Label("Builds", systemImage: "hammer")
}
+
+ NavigationStack(path: $morePath) {
+ MoreNavigationRoot()
+ }
+ .tag(AppState.Tab.more)
+ .tabItem {
+ Label("More", systemImage: "ellipsis.circle")
+ }
}
.overlay {
if isResolvingDeepLink {
@@ -118,7 +115,7 @@ struct RootView: View {
break
case .unauthenticated:
homePath = NavigationPath()
- inboxPath = NavigationPath()
+ morePath = NavigationPath()
repoPath = NavigationPath()
buildsPath = NavigationPath()
ticketsPath = NavigationPath()
@@ -135,6 +132,12 @@ struct RootView: View {
appState.pendingDeepLink = nil
}
+ private func consumePendingTabNavigationIfPossible(_ target: AppState.TabNavigationTarget?) {
+ guard appState.isAuthenticated, let target else { return }
+ handleTabNavigation(target)
+ appState.pendingTabNavigation = nil
+ }
+
private func handleDeepLink(_ link: DeepLink) {
guard appState.isAuthenticated else { return }
@@ -157,6 +160,36 @@ struct RootView: View {
}
}
+ private func handleTabNavigation(_ target: AppState.TabNavigationTarget) {
+ switch target {
+ case .repository(let repository):
+ repoPath = NavigationPath()
+ appState.selectedTab = .repositories
+ Task { @MainActor in
+ try? await Task.sleep(for: .milliseconds(100))
+ repoPath.append(repository)
+ }
+
+ case .tracker(let tracker):
+ ticketsPath = NavigationPath()
+ appState.selectedTab = .tickets
+ Task { @MainActor in
+ try? await Task.sleep(for: .milliseconds(100))
+ ticketsPath.append(tracker)
+ }
+
+ case .mailingList(let mailingList):
+ morePath = NavigationPath()
+ appState.selectedTab = .more
+ Task { @MainActor in
+ try? await Task.sleep(for: .milliseconds(100))
+ morePath.append(MoreRoute.lists)
+ try? await Task.sleep(for: .milliseconds(100))
+ morePath.append(MoreRoute.mailingList(mailingList))
+ }
+ }
+ }
+
private func resolveRepositoryLink(owner: String, repo: String) {
isResolvingDeepLink = true
Task {
@@ -198,6 +231,51 @@ struct RootView: View {
}
}
+enum MoreDestination: Hashable {
+ case lists
+ case pastes
+ case settings
+}
+
+enum MoreRoute: Hashable {
+ case lists
+ case pastes
+ case settings
+ case mailingList(InboxMailingListReference)
+ case thread(InboxThreadSummary)
+}
+
+private struct MoreNavigationRoot: View {
+ var body: some View {
+ MoreView()
+ .navigationDestination(for: MoreRoute.self) { route in
+ switch route {
+ case .lists:
+ MailingListListView()
+ case .pastes:
+ PasteListView()
+ case .settings:
+ SettingsView()
+ case .mailingList(let mailingList):
+ MailingListDetailView(mailingList: mailingList)
+ case .thread(let thread):
+ ThreadDetailView(
+ thread: thread,
+ onViewed: {
+ InboxReadStateStore.markViewed(max(Date(), thread.lastActivityAt), for: thread.id)
+ },
+ onMarkRead: {
+ InboxReadStateStore.markViewed(max(Date(), thread.lastActivityAt), for: thread.id)
+ },
+ onMarkUnread: {
+ InboxReadStateStore.markUnread(for: thread.id)
+ }
+ )
+ }
+ }
+ }
+}
+
// MARK: - Ticket Deep Link Navigation Target
/// Hashable wrapper to push a ticket detail view from a deep link.