feat: implement Live Activity registration service and enhance notifications handling

This commit is contained in:
2026-05-24 23:53:40 +07:00
parent d991d06f17
commit 802d32e9a0
13 changed files with 379 additions and 154 deletions
+55 -3
View File
@@ -14,9 +14,61 @@ struct AlertAttributes: ActivityAttributes {
let updatedAt: Date
enum CodingKeys: String, CodingKey {
case title, value, status
case startedAt = "startedAt"
case updatedAt = "updatedAt"
case title, value, status, startedAt, updatedAt
}
init(title: String, value: String?, status: AlertStatus, startedAt: Date, updatedAt: Date) {
self.title = title
self.value = value
self.status = status
self.startedAt = startedAt
self.updatedAt = updatedAt
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
title = try c.decode(String.self, forKey: .title)
value = try c.decodeIfPresent(String.self, forKey: .value)
status = try c.decode(AlertStatus.self, forKey: .status)
startedAt = try Self.decodeDate(from: c, forKey: .startedAt)
updatedAt = try Self.decodeDate(from: c, forKey: .updatedAt)
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(title, forKey: .title)
try c.encodeIfPresent(value, forKey: .value)
try c.encode(status, forKey: .status)
try c.encode(Self.isoFormatter.string(from: startedAt), forKey: .startedAt)
try c.encode(Self.isoFormatter.string(from: updatedAt), forKey: .updatedAt)
}
// ActivityKit uses the default JSONDecoder when materialising ContentState
// from a remote push payload, so dates round-trip as ISO8601 strings
// matching the backend's time.RFC3339[Nano] output. Both formatters are
// pre-built and treated as immutable; ISO8601DateFormatter is documented
// thread-safe for read use.
nonisolated(unsafe) private static let isoWithFractional: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return f
}()
nonisolated(unsafe) private static let isoWithoutFractional: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime]
return f
}()
// Single shared encoder format: ISO8601 without fractional seconds,
// since iOS reconstructs Date from string and sub-second precision is
// irrelevant for the alert timestamps we surface.
private static var isoFormatter: ISO8601DateFormatter { isoWithoutFractional }
private static func decodeDate(from c: KeyedDecodingContainer<CodingKeys>, forKey key: CodingKeys) throws -> Date {
let s = try c.decode(String.self, forKey: key)
if let d = isoWithFractional.date(from: s) ?? isoWithoutFractional.date(from: s) {
return d
}
throw DecodingError.dataCorruptedError(forKey: key, in: c, debugDescription: "Invalid ISO8601 date: \(s)")
}
}
}
+9 -3
View File
@@ -69,17 +69,23 @@ enum ContentType: String, Codable, Sendable {
case markdown
}
struct NotificationsPage: Codable, Sendable {
struct NotificationsList: Decodable, Sendable {
let notifications: [AppNotification]
let total: Int
let unreadCount: Int
enum CodingKeys: String, CodingKey {
case notifications, total
case notifications
case unreadCount = "unread_count"
}
}
struct NotificationsPage: Sendable {
let notifications: [AppNotification]
let unreadCount: Int
let total: Int
let hasMore: Bool
}
struct DeviceToken: Codable, Identifiable, Sendable {
let id: UUID
let userId: UUID