Faster / Cached ServerDate decoding

This commit is contained in:
Thomas Ricouard 2023-02-09 09:12:44 +01:00
parent 76c0c843cd
commit 013410a80e
10 changed files with 79 additions and 55 deletions

View file

@ -59,7 +59,7 @@ public struct Account: Codable, Identifiable, Equatable, Hashable {
header: URL(string: "https://files.mastodon.social/media_attachments/files/003/134/405/original/04060b07ddf7bb0b.png")!,
acct: "johnm@example.com",
note: .init(stringValue: "Some content"),
createdAt: "2022-12-16T10:20:54.000Z",
createdAt: ServerDate(),
followersCount: 10,
followingCount: 10,
statusesCount: 10,

View file

@ -1,13 +1,16 @@
import Foundation
public typealias ServerDate = String
fileprivate enum CodingKeys: CodingKey {
case asDate, relativeFormatted, shortDateFormatted
}
extension ServerDate {
public static var sampleDate: ServerDate {
createdAtDateFormatter.string(from: Date()-100)
}
public struct ServerDate: Codable, Hashable, Equatable {
public let asDate: Date
public let relativeFormatted: String
public let shortDateFormatted: String
private static let calendar = Calendar(identifier: .gregorian)
private static var createdAtDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.calendar = .init(identifier: .iso8601)
@ -15,31 +18,47 @@ extension ServerDate {
dateFormatter.timeZone = .init(abbreviation: "UTC")
return dateFormatter
}()
private static var createdAtRelativeFormatter: RelativeDateTimeFormatter = {
let dateFormatter = RelativeDateTimeFormatter()
dateFormatter.unitsStyle = .short
return dateFormatter
}()
private static var createdAtShortDateFormatted: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none
return dateFormatter
}()
private static let calendar = Calendar(identifier: .gregorian)
public var asDate: Date {
Self.createdAtDateFormatter.date(from: self)!
public init() {
asDate = Date()-100
relativeFormatted = Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date())
shortDateFormatted = Self.createdAtShortDateFormatted.string(from: asDate)
}
public var relativeFormatted: String {
return Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date())
public init(from decoder: Decoder) throws {
do {
// Decode from server
let container = try decoder.singleValueContainer()
let stringDate = try container.decode(String.self)
asDate = Self.createdAtDateFormatter.date(from: stringDate)!
relativeFormatted = Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date())
shortDateFormatted = Self.createdAtShortDateFormatted.string(from: asDate)
} catch {
// Decode from cache
let container = try decoder.container(keyedBy: CodingKeys.self)
asDate = try container.decode(Date.self, forKey: .asDate)
relativeFormatted = try container.decode(String.self, forKey: .relativeFormatted)
shortDateFormatted = try container.decode(String.self, forKey: .shortDateFormatted)
}
}
public var shortDateFormatted: String {
return Self.createdAtShortDateFormatted.string(from: asDate)
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(asDate, forKey: .asDate)
try container.encode(relativeFormatted, forKey: .relativeFormatted)
try container.encode(shortDateFormatted, forKey: .shortDateFormatted)
}
}

View file

@ -32,7 +32,7 @@ public struct ConsolidatedNotification: Identifiable {
public static func placeholder() -> ConsolidatedNotification {
.init(notifications: [Notification.placeholder()],
type: .favourite,
createdAt: "2022-12-16T10:20:54.000Z",
createdAt: ServerDate(),
accounts: [.placeholder()],
status: .placeholder())
}

View file

@ -18,7 +18,7 @@ public struct Notification: Decodable, Identifiable {
public static func placeholder() -> Notification {
.init(id: UUID().uuidString,
type: NotificationType.favourite.rawValue,
createdAt: "2022-12-16T10:20:54.000Z",
createdAt: ServerDate(),
account: .placeholder(),
status: .placeholder())
}

View file

@ -30,12 +30,12 @@ public struct Poll: Codable, Equatable, Hashable {
}
public struct NullableString: Codable, Equatable, Hashable {
public let value: String?
public let value: ServerDate?
public init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
value = try container.decode(String.self)
value = try container.decode(ServerDate.self)
} catch {
value = nil
}

View file

@ -54,15 +54,19 @@ public protocol AnyStatus {
var language: String? { get }
}
extension AnyStatus {
public var viewId: String {
get {
"\(id)\(createdAt.asDate.description)\(editedAt?.asDate.description ?? "")"
}
}
}
protocol StatusUI {
var userMentioned: Bool? { get set }
}
public struct Status: AnyStatus, Codable, Identifiable, Equatable, Hashable, StatusUI {
public var viewId: String {
id + createdAt + (editedAt ?? "")
}
public var userMentioned: Bool?
public static func == (lhs: Status, rhs: Status) -> Bool {
@ -105,7 +109,7 @@ public struct Status: AnyStatus, Codable, Identifiable, Equatable, Hashable, Sta
content: .init(stringValue: "Lorem ipsum [#dolor](#) sit amet\nconsectetur [@adipiscing](#) elit\nAsed do eiusmod tempor incididunt ut labore.", parseMarkdown: forSettings),
account: .placeholder(),
createdAt: ServerDate.sampleDate,
createdAt: ServerDate(),
editedAt: nil,
reblog: nil,
mediaAttachments: [],
@ -137,10 +141,6 @@ public struct Status: AnyStatus, Codable, Identifiable, Equatable, Hashable, Sta
}
public struct ReblogStatus: AnyStatus, Codable, Identifiable, Equatable, Hashable {
public var viewId: String {
id + createdAt + (editedAt ?? "")
}
public static func == (lhs: ReblogStatus, rhs: ReblogStatus) -> Bool {
lhs.id == rhs.id
}
@ -152,7 +152,7 @@ public struct ReblogStatus: AnyStatus, Codable, Identifiable, Equatable, Hashabl
public let id: String
public let content: HTMLString
public let account: Account
public let createdAt: String
public let createdAt: ServerDate
public let editedAt: ServerDate?
public let mediaAttachments: [MediaAttachment]
public let mentions: [Mention]

View file

@ -2,7 +2,7 @@ import Foundation
public struct StatusHistory: Decodable, Identifiable {
public var id: String {
createdAt.description
createdAt.asDate.description
}
public let content: HTMLString

View file

@ -23,7 +23,7 @@ extension Array where Element == Notification {
status: notification.status)
}
.sorted {
$0.createdAt > $1.createdAt
$0.createdAt.asDate > $1.createdAt.asDate
}
}
}

View file

@ -96,24 +96,7 @@ public struct StatusRowView: View {
}
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
.accessibilityActions {
// Add the individual mentions as accessibility actions
ForEach(viewModel.status.mentions, id: \.id) { mention in
Button("@\(mention.username)") {
routerPath.navigate(to: .accountDetail(id: mention.id))
}
}
Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") {
withAnimation {
viewModel.displaySpoiler.toggle()
}
}
Button("@\(viewModel.status.account.username)") {
routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
}
contextMenu
accesibilityActions
}
.background {
Color.clear
@ -144,6 +127,28 @@ public struct StatusRowView: View {
}
}
}
@ViewBuilder
private var accesibilityActions: some View {
// Add the individual mentions as accessibility actions
ForEach(viewModel.status.mentions, id: \.id) { mention in
Button("@\(mention.username)") {
routerPath.navigate(to: .accountDetail(id: mention.id))
}
}
Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") {
withAnimation {
viewModel.displaySpoiler.toggle()
}
}
Button("@\(viewModel.status.account.username)") {
routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
}
contextMenu
}
private func makeFilterView(filter: Filter) -> some View {
HStack {

View file

@ -45,7 +45,7 @@ public actor TimelineCache {
return try await engine
.readAllData()
.map { try decoder.decode(Status.self, from: $0) }
.sorted(by: { $0.createdAt > $1.createdAt })
.sorted(by: { $0.createdAt.asDate > $1.createdAt.asDate })
} catch {
return nil
}