summaryrefslogtreecommitdiff
path: root/Shared
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-12 23:19:20 -0500
committerChristian Cleberg <[email protected]>2026-04-12 23:19:20 -0500
commitee9f2904aa319231b7047fca3cb069f0c07019cd (patch)
tree69a18234512c63f38f0d477e0023969698ec3c9e /Shared
parent72f860e0e171f6aac4e7d896e7a571147dbcb5c5 (diff)
downloadhutch-ee9f2904aa319231b7047fca3cb069f0c07019cd.tar.gz
hutch-ee9f2904aa319231b7047fca3cb069f0c07019cd.tar.bz2
hutch-ee9f2904aa319231b7047fca3cb069f0c07019cd.zip
feat: add multi-account support
Implements: https://todo.sr.ht/~ccleberg/hutch/49 Implements: https://todo.sr.ht/~ccleberg/hutch/50 Implements: https://todo.sr.ht/~ccleberg/hutch/51
Diffstat (limited to 'Shared')
-rw-r--r--Shared/ContributionWidgetContext.swift27
-rw-r--r--Shared/NeedsAttentionSnapshot.swift56
-rw-r--r--Shared/SystemStatusWidgetSnapshot.swift27
3 files changed, 89 insertions, 21 deletions
diff --git a/Shared/ContributionWidgetContext.swift b/Shared/ContributionWidgetContext.swift
index 2404698..33fed77 100644
--- a/Shared/ContributionWidgetContext.swift
+++ b/Shared/ContributionWidgetContext.swift
@@ -11,8 +11,11 @@ enum ContributionWidgetContextStore {
private static let actorKey = "contributionWidget.actor"
private static let enabledKey = "contributionWidget.enabled"
- static func loadActor(defaults: UserDefaults? = sharedDefaults()) -> String? {
- defaults?.string(forKey: actorKey)
+ static func loadActor(
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) -> String? {
+ defaults?.string(forKey: scopedActorKey(for: accountID))
}
static func isEnabled(defaults: UserDefaults? = sharedDefaults()) -> Bool {
@@ -24,13 +27,20 @@ enum ContributionWidgetContextStore {
reloadWidgetTimelines()
}
- static func saveActor(_ actor: String, defaults: UserDefaults? = sharedDefaults()) {
- defaults?.set(actor, forKey: actorKey)
+ static func saveActor(
+ _ actor: String,
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) {
+ defaults?.set(actor, forKey: scopedActorKey(for: accountID))
reloadWidgetTimelines()
}
- static func clear(defaults: UserDefaults? = sharedDefaults()) {
- defaults?.removeObject(forKey: actorKey)
+ static func clear(
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) {
+ defaults?.removeObject(forKey: scopedActorKey(for: accountID))
reloadWidgetTimelines()
}
@@ -38,6 +48,11 @@ enum ContributionWidgetContextStore {
UserDefaults(suiteName: HutchAppGroup.identifier)
}
+ private static func scopedActorKey(for accountID: String?) -> String {
+ guard let accountID, !accountID.isEmpty else { return actorKey }
+ return "\(actorKey).\(accountID)"
+ }
+
private static func reloadWidgetTimelines() {
#if canImport(WidgetKit)
WidgetCenter.shared.reloadTimelines(ofKind: ContributionGraphWidgetConfiguration.kind)
diff --git a/Shared/NeedsAttentionSnapshot.swift b/Shared/NeedsAttentionSnapshot.swift
index f386af6..d4c581c 100644
--- a/Shared/NeedsAttentionSnapshot.swift
+++ b/Shared/NeedsAttentionSnapshot.swift
@@ -7,6 +7,26 @@ enum HutchAppGroup {
static let identifier = "group.net.cleberg.Hutch"
}
+enum ActiveAccountContextStore {
+ private static let activeAccountIDKey = "activeAccount.id"
+
+ static func load(defaults: UserDefaults? = sharedDefaults()) -> String? {
+ defaults?.string(forKey: activeAccountIDKey)
+ }
+
+ static func save(_ accountID: String, defaults: UserDefaults? = sharedDefaults()) {
+ defaults?.set(accountID, forKey: activeAccountIDKey)
+ }
+
+ static func clear(defaults: UserDefaults? = sharedDefaults()) {
+ defaults?.removeObject(forKey: activeAccountIDKey)
+ }
+
+ private static func sharedDefaults() -> UserDefaults? {
+ UserDefaults(suiteName: HutchAppGroup.identifier)
+ }
+}
+
enum NeedsAttentionWidgetConfiguration {
static let kind = "NeedsAttentionWidget"
}
@@ -37,22 +57,29 @@ struct NeedsAttentionSnapshot: Codable, Sendable {
enum NeedsAttentionSnapshotStore {
private static let snapshotKey = "needsAttention.snapshot"
- static func load(defaults: UserDefaults? = sharedDefaults()) -> NeedsAttentionSnapshot? {
+ static func load(
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) -> NeedsAttentionSnapshot? {
guard let defaults,
- let data = defaults.data(forKey: snapshotKey) else {
+ let data = defaults.data(forKey: scopedKey(for: accountID)) else {
return nil
}
return try? JSONDecoder().decode(NeedsAttentionSnapshot.self, from: data)
}
- static func save(_ snapshot: NeedsAttentionSnapshot, defaults: UserDefaults? = sharedDefaults()) {
+ static func save(
+ _ snapshot: NeedsAttentionSnapshot,
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) {
guard let defaults,
let data = try? JSONEncoder().encode(snapshot) else {
return
}
- defaults.set(data, forKey: snapshotKey)
+ defaults.set(data, forKey: scopedKey(for: accountID))
reloadWidgetTimelines()
}
@@ -60,23 +87,25 @@ enum NeedsAttentionSnapshotStore {
unreadInboxThreads: Int? = nil,
assignedOpenTickets: Int? = nil,
failedBuilds: Int? = nil,
+ accountID: String? = ActiveAccountContextStore.load(),
defaults: UserDefaults? = sharedDefaults()
) {
- let existing = load(defaults: defaults)
+ let existing = load(accountID: accountID, defaults: defaults)
let snapshot = NeedsAttentionSnapshot(
unreadInboxThreads: unreadInboxThreads ?? existing?.unreadInboxThreads,
assignedOpenTickets: assignedOpenTickets ?? existing?.assignedOpenTickets,
failedBuilds: failedBuilds ?? existing?.failedBuilds,
updatedAt: .now
)
- save(snapshot, defaults: defaults)
+ save(snapshot, accountID: accountID, defaults: defaults)
}
static func adjustUnreadInboxThreads(
by delta: Int,
+ accountID: String? = ActiveAccountContextStore.load(),
defaults: UserDefaults? = sharedDefaults()
) {
- guard let existing = load(defaults: defaults),
+ guard let existing = load(accountID: accountID, defaults: defaults),
let unreadInboxThreads = existing.unreadInboxThreads else {
return
}
@@ -88,12 +117,16 @@ enum NeedsAttentionSnapshotStore {
failedBuilds: existing.failedBuilds,
updatedAt: .now
),
+ accountID: accountID,
defaults: defaults
)
}
- static func clear(defaults: UserDefaults? = sharedDefaults()) {
- defaults?.removeObject(forKey: snapshotKey)
+ static func clear(
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) {
+ defaults?.removeObject(forKey: scopedKey(for: accountID))
reloadWidgetTimelines()
}
@@ -101,6 +134,11 @@ enum NeedsAttentionSnapshotStore {
UserDefaults(suiteName: HutchAppGroup.identifier)
}
+ private static func scopedKey(for accountID: String?) -> String {
+ guard let accountID, !accountID.isEmpty else { return snapshotKey }
+ return "\(snapshotKey).\(accountID)"
+ }
+
private static func reloadWidgetTimelines() {
#if canImport(WidgetKit)
WidgetCenter.shared.reloadTimelines(ofKind: NeedsAttentionWidgetConfiguration.kind)
diff --git a/Shared/SystemStatusWidgetSnapshot.swift b/Shared/SystemStatusWidgetSnapshot.swift
index 8489547..0b19365 100644
--- a/Shared/SystemStatusWidgetSnapshot.swift
+++ b/Shared/SystemStatusWidgetSnapshot.swift
@@ -33,25 +33,35 @@ struct SystemStatusWidgetSnapshot: Codable, Sendable {
enum SystemStatusWidgetSnapshotStore {
private static let snapshotKey = "systemStatus.widgetSnapshot"
- static func load(defaults: UserDefaults? = sharedDefaults()) -> SystemStatusWidgetSnapshot? {
+ static func load(
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) -> SystemStatusWidgetSnapshot? {
guard let defaults,
- let data = defaults.data(forKey: snapshotKey) else {
+ let data = defaults.data(forKey: scopedKey(for: accountID)) else {
return nil
}
return try? JSONDecoder().decode(SystemStatusWidgetSnapshot.self, from: data)
}
- static func save(_ snapshot: SystemStatusWidgetSnapshot, defaults: UserDefaults? = sharedDefaults()) {
+ static func save(
+ _ snapshot: SystemStatusWidgetSnapshot,
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) {
guard let defaults,
let data = try? JSONEncoder().encode(snapshot) else {
return
}
- defaults.set(data, forKey: snapshotKey)
+ defaults.set(data, forKey: scopedKey(for: accountID))
reloadWidgetTimelines()
}
- static func clear(defaults: UserDefaults? = sharedDefaults()) {
- defaults?.removeObject(forKey: snapshotKey)
+ static func clear(
+ accountID: String? = ActiveAccountContextStore.load(),
+ defaults: UserDefaults? = sharedDefaults()
+ ) {
+ defaults?.removeObject(forKey: scopedKey(for: accountID))
reloadWidgetTimelines()
}
@@ -59,6 +69,11 @@ enum SystemStatusWidgetSnapshotStore {
UserDefaults(suiteName: HutchAppGroup.identifier)
}
+ private static func scopedKey(for accountID: String?) -> String {
+ guard let accountID, !accountID.isEmpty else { return snapshotKey }
+ return "\(snapshotKey).\(accountID)"
+ }
+
private static func reloadWidgetTimelines() {
#if canImport(WidgetKit)
WidgetCenter.shared.reloadTimelines(ofKind: SystemStatusWidgetConfiguration.kind)