crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 29be035ec7271405e9b2762c4620d11ccc019925
parent 0c7c77acb70c2414977190c00b3d0776666a1363
Author: Michael Camilleri <[email protected]>
Date:   Thu, 14 May 2026 08:24:07 +0900

Display log events in-app using local time

Diffstat:
MCrossmate/Views/DiagnosticsView.swift | 55+++++++++++++++++++++++++++++++++++--------------------
1 file changed, 35 insertions(+), 20 deletions(-)

diff --git a/Crossmate/Views/DiagnosticsView.swift b/Crossmate/Views/DiagnosticsView.swift @@ -1,13 +1,20 @@ import CloudKit import SwiftUI -struct DiagnosticsView: View { - @Environment(\.syncEngine) private var syncEngine - @Environment(SyncMonitor.self) private var syncMonitor +private enum TimestampTimeZone { + case local + case utc +} - @State private var isSyncing = false +private enum TimestampFormatter { + private static let localFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .medium + return formatter + }() - private static let timestampFormatter: DateFormatter = { + private static let utcFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .none formatter.timeStyle = .medium @@ -15,6 +22,22 @@ struct DiagnosticsView: View { return formatter }() + static func string(from date: Date, in timeZone: TimestampTimeZone) -> String { + switch timeZone { + case .local: + return localFormatter.string(from: date) + case .utc: + return utcFormatter.string(from: date) + } + } +} + +struct DiagnosticsView: View { + @Environment(\.syncEngine) private var syncEngine + @Environment(SyncMonitor.self) private var syncMonitor + + @State private var isSyncing = false + var body: some View { List { Section("Status") { @@ -23,7 +46,7 @@ struct DiagnosticsView: View { row("Pending Changes", syncMonitor.snapshot.map { String($0.pendingChangesCount) } ?? "Unknown") row( "Last Success", - syncMonitor.lastSuccessAt.map(Self.timestampFormatter.string(from:)) ?? "None" + syncMonitor.lastSuccessAt.map { TimestampFormatter.string(from: $0, in: .local) } ?? "None" ) row("Last Error Phase", syncMonitor.lastErrorPhase ?? "None") row("Last Error Domain", syncMonitor.lastErrorDomain ?? "None") @@ -67,7 +90,7 @@ struct DiagnosticsView: View { ForEach(syncMonitor.entries.reversed()) { entry in VStack(alignment: .leading, spacing: 4) { Text( - "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())]" + "\(TimestampFormatter.string(from: entry.timestamp, in: .local)) [\(entry.level.uppercased())]" ) .font(.caption.monospaced()) .foregroundStyle(.secondary) @@ -170,7 +193,7 @@ struct DiagnosticsView: View { lines.append("Engine Running: \(boolText(syncMonitor.snapshot?.engineRunning))") lines.append("Pending Changes: \(syncMonitor.snapshot.map { String($0.pendingChangesCount) } ?? "Unknown")") lines.append( - "Last Success: \(syncMonitor.lastSuccessAt.map(Self.timestampFormatter.string(from:)) ?? "None")" + "Last Success: \(syncMonitor.lastSuccessAt.map { TimestampFormatter.string(from: $0, in: .utc) } ?? "None")" ) lines.append("Last Error Phase: \(syncMonitor.lastErrorPhase ?? "None")") lines.append("Last Error Domain: \(syncMonitor.lastErrorDomain ?? "None")") @@ -181,7 +204,7 @@ struct DiagnosticsView: View { lines.append("Recent Events (UTC):") for entry in syncMonitor.entries { lines.append( - "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())] \(entry.message)" + "\(TimestampFormatter.string(from: entry.timestamp, in: .utc)) [\(entry.level.uppercased())] \(entry.message)" ) } return lines.joined(separator: "\n") @@ -191,14 +214,6 @@ struct DiagnosticsView: View { struct ShareDiagnosticsView: View { @Environment(SyncMonitor.self) private var syncMonitor - private static let timestampFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .none - formatter.timeStyle = .medium - formatter.timeZone = TimeZone(secondsFromGMT: 0) - return formatter - }() - private var shareEntries: [SyncDiagnosticEntry] { syncMonitor.entries.filter { $0.message.localizedCaseInsensitiveContains("share") @@ -222,7 +237,7 @@ struct ShareDiagnosticsView: View { ForEach(shareEntries.reversed()) { entry in VStack(alignment: .leading, spacing: 4) { Text( - "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())]" + "\(TimestampFormatter.string(from: entry.timestamp, in: .local)) [\(entry.level.uppercased())]" ) .font(.caption.monospaced()) .foregroundStyle(.secondary) @@ -267,10 +282,10 @@ struct ShareDiagnosticsView: View { lines.append("Last Share Error Code: \(syncMonitor.lastErrorCode.map(String.init) ?? "None")") lines.append("Last Share Error Description: \(syncMonitor.lastErrorDescription ?? "None")") lines.append("") - lines.append("Share Events:") + lines.append("Share Events (UTC):") for entry in shareEntries { lines.append( - "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())] \(entry.message)" + "\(TimestampFormatter.string(from: entry.timestamp, in: .utc)) [\(entry.level.uppercased())] \(entry.message)" ) } return lines.joined(separator: "\n")