diff options
| author | Christian Cleberg <[email protected]> | 2026-04-13 19:44:44 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-13 19:44:44 -0500 |
| commit | fe1ccc69661603d419a642a450e4ac9e1258adb0 (patch) | |
| tree | 386455b6b5468abdd9dfa46dd4802b10da8b77a2 /Hutch/Models/ContributionCalendar.swift | |
| parent | 6ec9754ce34f331641b000eddef3d44bca2631a5 (diff) | |
| download | hutch-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/ContributionCalendar.swift')
| -rw-r--r-- | Hutch/Models/ContributionCalendar.swift | 92 |
1 files changed, 57 insertions, 35 deletions
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 + ) + ) } } |
