summaryrefslogtreecommitdiff
path: root/Hutch/Views/Projects
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-13 13:09:57 -0500
committerChristian Cleberg <[email protected]>2026-04-13 13:09:57 -0500
commitf5f5757d0e1b78470429ae7cb9f742c95662849b (patch)
tree758f67965a985f4f1f52a456d9b672f8256851de /Hutch/Views/Projects
parent0e461a382257979037e8927e5f6b468635b0a7fe (diff)
downloadhutch-f5f5757d0e1b78470429ae7cb9f742c95662849b.tar.gz
hutch-f5f5757d0e1b78470429ae7cb9f742c95662849b.tar.bz2
hutch-f5f5757d0e1b78470429ae7cb9f742c95662849b.zip
release: v3.0.0 — navigation overhaul, Work view, and multi-pin supportv3.0.0
Reworks the app's core navigation and home screen for v3.0.0: - Replace Inbox with Work view: unified dashboard showing unread threads and assigned tickets, with All/Unread/Assigned scope picker - Overhaul Home screen: redesigned with pinned items grid (trackers, repos, mailing lists, users), recent activity section, and system status banner; backed by HomePinStore supporting all resource types - Simplify navigation bars across list screens: default toolbar shows only the primary action (+) and an overflow menu (…); pin, share, and select moved into the overflow menu; selection mode gets its own nav bar with Cancel / "N Selected" / All - Consolidate repo detail toolbars: pin and share moved into the existing actions menu for both Git and Mercurial detail views - Add recent activity tracking via RecentActivityStore - Add work and inbox deep link aliases (hutch://work, hutch://inbox) Implements: https://todo.sr.ht/~ccleberg/hutch/55
Diffstat (limited to 'Hutch/Views/Projects')
-rw-r--r--Hutch/Views/Projects/ProjectMailingListView.swift29
-rw-r--r--Hutch/Views/Projects/ProjectPinStore.swift29
2 files changed, 58 insertions, 0 deletions
diff --git a/Hutch/Views/Projects/ProjectMailingListView.swift b/Hutch/Views/Projects/ProjectMailingListView.swift
index 478e471..4ff93e9 100644
--- a/Hutch/Views/Projects/ProjectMailingListView.swift
+++ b/Hutch/Views/Projects/ProjectMailingListView.swift
@@ -229,6 +229,17 @@ struct MailingListDetailView: View {
@Environment(AppState.self) private var appState
@State private var viewModel: MailingListDetailViewModel?
+ @State private var pinChangeCount = 0
+
+ private var currentUserKey: String? {
+ appState.currentUser?.canonicalName
+ }
+
+ private var isPinnedToHome: Bool {
+ _ = pinChangeCount
+ guard let currentUserKey else { return false }
+ return HomePinStore.isPinned(.mailingList(mailingList), for: currentUserKey, defaults: appState.accountDefaults)
+ }
var body: some View {
Group {
@@ -240,6 +251,18 @@ struct MailingListDetailView: View {
}
.navigationTitle(mailingList.name)
.navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ if currentUserKey != nil {
+ ToolbarItem(placement: .topBarTrailing) {
+ Button {
+ togglePinnedState()
+ } label: {
+ Image(systemName: isPinnedToHome ? "pin.fill" : "pin")
+ }
+ .accessibilityLabel(isPinnedToHome ? "Unpin from Home" : "Pin to Home")
+ }
+ }
+ }
.task {
if viewModel == nil {
let viewModel = MailingListDetailViewModel(
@@ -260,6 +283,12 @@ struct MailingListDetailView: View {
}
}
+ private func togglePinnedState() {
+ guard let currentUserKey else { return }
+ HomePinStore.togglePin(.mailingList(mailingList), for: currentUserKey, defaults: appState.accountDefaults)
+ pinChangeCount += 1
+ }
+
@ViewBuilder
private func content(_ viewModel: MailingListDetailViewModel) -> some View {
@Bindable var vm = viewModel
diff --git a/Hutch/Views/Projects/ProjectPinStore.swift b/Hutch/Views/Projects/ProjectPinStore.swift
index 77a4172..ed7e386 100644
--- a/Hutch/Views/Projects/ProjectPinStore.swift
+++ b/Hutch/Views/Projects/ProjectPinStore.swift
@@ -5,6 +5,11 @@ enum ProjectPinStore {
for userKey: String,
defaults: UserDefaults = .standard
) -> [String] {
+ let generalizedPins = HomePinStore.pinnedProjectIDs(for: userKey, defaults: defaults)
+ if !generalizedPins.isEmpty {
+ return generalizedPins
+ }
+
let pinnedProjects = loadAll(defaults: defaults)
return normalizedProjectIDs(pinnedProjects[userKey] ?? [])
}
@@ -22,6 +27,22 @@ enum ProjectPinStore {
for userKey: String,
defaults: UserDefaults = .standard
) {
+ let trimmed = projectID.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !trimmed.isEmpty else { return }
+
+ HomePinStore.togglePin(
+ HomePinRecord(
+ kind: .project,
+ value: trimmed,
+ title: "Pinned Project",
+ subtitle: "Project",
+ ownerUsername: nil,
+ service: nil
+ ),
+ for: userKey,
+ defaults: defaults
+ )
+
var pinnedProjects = loadAll(defaults: defaults)
var projectIDs = normalizedProjectIDs(pinnedProjects[userKey] ?? [])
@@ -37,6 +58,14 @@ enum ProjectPinStore {
save(pinnedProjects, defaults: defaults)
}
+ static func loadLegacyPinnedProjectIDs(
+ for userKey: String,
+ defaults: UserDefaults = .standard
+ ) -> [String] {
+ let pinnedProjects = loadAll(defaults: defaults)
+ return normalizedProjectIDs(pinnedProjects[userKey] ?? [])
+ }
+
private static func loadAll(defaults: UserDefaults) -> [String: [String]] {
guard let data = defaults.data(forKey: AppStorageKeys.pinnedHomeProjects) else {
return [:]