From f5f5757d0e1b78470429ae7cb9f742c95662849b Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Mon, 13 Apr 2026 13:09:57 -0500 Subject: release: v3.0.0 — navigation overhaul, Work view, and multi-pin support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Hutch/Views/Lookup/UserProfileView.swift | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'Hutch/Views/Lookup/UserProfileView.swift') diff --git a/Hutch/Views/Lookup/UserProfileView.swift b/Hutch/Views/Lookup/UserProfileView.swift index 84375de..4fbb5a0 100644 --- a/Hutch/Views/Lookup/UserProfileView.swift +++ b/Hutch/Views/Lookup/UserProfileView.swift @@ -6,6 +6,7 @@ struct UserProfileView: View { let user: User @State private var profileViewModel: UserProfileViewModel? + @State private var pinChangeCount = 0 private static let iso8601Formatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() @@ -14,6 +15,16 @@ struct UserProfileView: View { return formatter }() + private var currentUserKey: String? { + appState.currentUser?.canonicalName + } + + private var isPinnedToHome: Bool { + _ = pinChangeCount + guard let currentUserKey else { return false } + return HomePinStore.isPinned(.user(user), for: currentUserKey, defaults: appState.accountDefaults) + } + var body: some View { List { if let avatarURL = user.avatar.flatMap(URL.init(string:)) { @@ -164,6 +175,18 @@ struct UserProfileView: View { .listStyle(.insetGrouped) .navigationTitle(user.canonicalName) .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(id: user.canonicalName) { let owner = user.canonicalName.hasPrefix("~") ? String(user.canonicalName.dropFirst()) @@ -200,6 +223,12 @@ struct UserProfileView: View { } } + private func togglePinnedState() { + guard let currentUserKey else { return } + HomePinStore.togglePin(.user(user), for: currentUserKey, defaults: appState.accountDefaults) + pinChangeCount += 1 + } + private func formattedTimestamp(_ value: String) -> String { guard let date = Self.iso8601Formatter.date(from: value) else { return value -- cgit v1.2.3