summaryrefslogtreecommitdiff
path: root/Hutch/Models
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-13 19:44:44 -0500
committerChristian Cleberg <[email protected]>2026-04-13 19:44:44 -0500
commitfe1ccc69661603d419a642a450e4ac9e1258adb0 (patch)
tree386455b6b5468abdd9dfa46dd4802b10da8b77a2 /Hutch/Models
parent6ec9754ce34f331641b000eddef3d44bca2631a5 (diff)
downloadhutch-3.1.3.tar.gz
hutch-3.1.3.tar.bz2
hutch-3.1.3.zip
fix: centralize URLs, tighten models, and polish SwiftUI bindingsv3.1.3
- Add shared HutchDeepLinkURL constants for app, widgets, and tests. - Bump marketing version to 3.1.2 and build to 70 (app + widget extension). - Point Hutch stats default base URL at HutchStatsAPI; add SRHTWebURL status incident feed and reuse it from SystemStatusService. - Group Project into Metadata and Resources; split ContributionStatsResponse into StatsWindow and StatsTotals with updated decoding and tests. - Replace @Bindable usage with explicit Bindings in Profile, Projects list, and repository ACL flows; simplify Home pinned-item helper; add no-op bodies on cancel alert buttons where the compiler requires a statement. - Move repository row build-status indicator next to the relative-updated caption and reserve a fixed 8×8 slot so the row does not jump when status loads. - Use NSString.lastPathComponent for build artifact filenames; collapse duplicate ACL error branches; minor HutchStats HTTP and XMLParserDelegate cleanups. - Point widgets at HutchDeepLinkURL helpers; align tests with the new response and URL types.
Diffstat (limited to 'Hutch/Models')
-rw-r--r--Hutch/Models/Builds.swift3
-rw-r--r--Hutch/Models/ContributionCalendar.swift92
-rw-r--r--Hutch/Models/Project.swift54
3 files changed, 88 insertions, 61 deletions
diff --git a/Hutch/Models/Builds.swift b/Hutch/Models/Builds.swift
index d666398..3926888 100644
--- a/Hutch/Models/Builds.swift
+++ b/Hutch/Models/Builds.swift
@@ -136,8 +136,7 @@ struct BuildArtifact: Codable, Sendable, Identifiable, Equatable {
let url: URL?
var filename: String {
- let pathComponents = path.split(separator: "/")
- return pathComponents.last.map(String.init) ?? path
+ (path as NSString).lastPathComponent
}
var isDownloadable: Bool {
diff --git a/Hutch/Models/ContributionCalendar.swift b/Hutch/Models/ContributionCalendar.swift
index e2942dd..a429fa2 100644
--- a/Hutch/Models/ContributionCalendar.swift
+++ b/Hutch/Models/ContributionCalendar.swift
@@ -117,45 +117,67 @@ struct ContributionStatsResponse: Decodable, Sendable, Hashable {
case currentStreak = "current_streak"
}
- init(
- actor: String,
- from: Date,
- to: Date,
- isIndexed: Bool,
- lastPolledAt: Date?,
- indexingState: ContributionIndexingState,
- totalEvents: Int,
- totalScore: Double,
- activeDays: Int,
- longestStreak: Int,
- currentStreak: Int
- ) {
- self.actor = actor
- self.from = from
- self.to = to
- self.isIndexed = isIndexed
- self.lastPolledAt = lastPolledAt
- self.indexingState = indexingState
- self.totalEvents = totalEvents
- self.totalScore = totalScore
- self.activeDays = activeDays
- self.longestStreak = longestStreak
- self.currentStreak = currentStreak
+ struct StatsWindow: Sendable, Hashable {
+ let actor: String
+ let from: Date
+ let to: Date
+ let isIndexed: Bool
+ let lastPolledAt: Date?
+ let indexingState: ContributionIndexingState
+ }
+
+ struct StatsTotals: Sendable, Hashable {
+ let totalEvents: Int
+ let totalScore: Double
+ let activeDays: Int
+ let longestStreak: Int
+ let currentStreak: Int
+ }
+
+ init(window: StatsWindow, totals: StatsTotals) {
+ actor = window.actor
+ from = window.from
+ to = window.to
+ isIndexed = window.isIndexed
+ lastPolledAt = window.lastPolledAt
+ indexingState = window.indexingState
+ totalEvents = totals.totalEvents
+ totalScore = totals.totalScore
+ activeDays = totals.activeDays
+ longestStreak = totals.longestStreak
+ currentStreak = totals.currentStreak
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
- actor = try container.decode(String.self, forKey: .actor)
- from = try ContributionDateParser.decodeDateString(from: container, forKey: .from)
- to = try ContributionDateParser.decodeDateString(from: container, forKey: .to)
- isIndexed = try container.decodeIfPresent(Bool.self, forKey: .isIndexed) ?? false
- lastPolledAt = try ContributionDateParser.decodeOptionalTimestamp(from: container, forKey: .lastPolledAt)
- indexingState = try container.decodeIfPresent(ContributionIndexingState.self, forKey: .indexingState) ?? .indexed
- totalEvents = try container.decode(Int.self, forKey: .totalEvents)
- totalScore = try container.decode(Double.self, forKey: .totalScore)
- activeDays = try container.decode(Int.self, forKey: .activeDays)
- longestStreak = try container.decode(Int.self, forKey: .longestStreak)
- currentStreak = try container.decode(Int.self, forKey: .currentStreak)
+ let actor = try container.decode(String.self, forKey: .actor)
+ let from = try ContributionDateParser.decodeDateString(from: container, forKey: .from)
+ let to = try ContributionDateParser.decodeDateString(from: container, forKey: .to)
+ let isIndexed = try container.decodeIfPresent(Bool.self, forKey: .isIndexed) ?? false
+ let lastPolledAt = try ContributionDateParser.decodeOptionalTimestamp(from: container, forKey: .lastPolledAt)
+ let indexingState = try container.decodeIfPresent(ContributionIndexingState.self, forKey: .indexingState) ?? .indexed
+ let totalEvents = try container.decode(Int.self, forKey: .totalEvents)
+ let totalScore = try container.decode(Double.self, forKey: .totalScore)
+ let activeDays = try container.decode(Int.self, forKey: .activeDays)
+ let longestStreak = try container.decode(Int.self, forKey: .longestStreak)
+ let currentStreak = try container.decode(Int.self, forKey: .currentStreak)
+ self.init(
+ window: .init(
+ actor: actor,
+ from: from,
+ to: to,
+ isIndexed: isIndexed,
+ lastPolledAt: lastPolledAt,
+ indexingState: indexingState
+ ),
+ totals: .init(
+ totalEvents: totalEvents,
+ totalScore: totalScore,
+ activeDays: activeDays,
+ longestStreak: longestStreak,
+ currentStreak: currentStreak
+ )
+ )
}
}
diff --git a/Hutch/Models/Project.swift b/Hutch/Models/Project.swift
index 678d52e..bbca7d6 100644
--- a/Hutch/Models/Project.swift
+++ b/Hutch/Models/Project.swift
@@ -71,30 +71,36 @@ struct Project: Identifiable, Hashable, Sendable {
let trackers: [Tracker]
let isFullyLoaded: Bool
- init(
- id: String,
- name: String,
- description: String?,
- website: String?,
- visibility: Visibility,
- tags: [String],
- updated: Date,
- mailingLists: [MailingList],
- sources: [SourceRepo],
- trackers: [Tracker],
- isFullyLoaded: Bool = true
- ) {
- self.id = id
- self.name = name
- self.description = description
- self.website = website
- self.visibility = visibility
- self.tags = tags
- self.updated = updated
- self.mailingLists = mailingLists
- self.sources = sources
- self.trackers = trackers
- self.isFullyLoaded = isFullyLoaded
+ /// Identity and display fields for a project.
+ struct Metadata: Sendable, Hashable {
+ let id: String
+ let name: String
+ let description: String?
+ let website: String?
+ let visibility: Visibility
+ let tags: [String]
+ let updated: Date
+ }
+
+ struct Resources: Sendable, Hashable {
+ let mailingLists: [MailingList]
+ let sources: [SourceRepo]
+ let trackers: [Tracker]
+ let isFullyLoaded: Bool
+ }
+
+ init(metadata: Metadata, resources: Resources) {
+ id = metadata.id
+ name = metadata.name
+ description = metadata.description
+ website = metadata.website
+ visibility = metadata.visibility
+ tags = metadata.tags
+ updated = metadata.updated
+ mailingLists = resources.mailingLists
+ sources = resources.sources
+ trackers = resources.trackers
+ isFullyLoaded = resources.isFullyLoaded
}
var resourceSummary: String? {