TeraTodayCardPresentation.swift (2665B)
1 import Foundation 2 3 enum TeraTodayCardPresentation { 4 case feed 5 case detail 6 7 static let excerptCharacters = 640 8 static let excerptBytes = 4096 9 static let labelCharacters = 160 10 static let labelBytes = 1024 11 static let thumbnails = 3 12 13 func content(_ value: String) -> String { 14 self == .detail ? value : Self.excerpt(value, characters: Self.excerptCharacters, bytes: Self.excerptBytes) 15 } 16 17 func label(_ value: String) -> String { 18 self == .detail ? value : Self.excerpt(value, characters: Self.labelCharacters, bytes: Self.labelBytes) 19 } 20 21 func media(_ values: [TeraMediaReference]) -> [TeraMediaReference] { 22 self == .detail ? values : Array(values.prefix(Self.thumbnails)) 23 } 24 25 func accessibility(_ card: TeraTodayCard, locale: Locale = .current, timeZone: TimeZone = .current) -> String { 26 if self == .detail { 27 return card.accessibilitySummary(locale: locale, timeZone: timeZone) 28 } 29 var parts = [card.type.label, "by \(label(card.authorName))"] 30 if let title = card.title { 31 parts.append(label(title)) 32 } 33 parts.append(content(card.content)) 34 if let timing = card.calendarTiming { 35 parts.append(TeraCalendarPresentation(locale: locale, timeZone: timeZone).summary(timing)) 36 } 37 let food = TeraFoodPresentation(locale: locale) 38 if let price = food.price(card) { 39 parts.append(label(price)) 40 } 41 if let quantity = food.quantity(card) { 42 parts.append(label(quantity)) 43 } 44 if card.lifecycle != .active { 45 parts.append(card.lifecycle.rawValue) 46 } 47 if let operation = card.localOperationState { 48 parts.append(label(operation)) 49 } 50 return parts.joined(separator: ", ") 51 } 52 53 /// Bound bytes before walking graphemes: a single combining sequence can be 54 /// arbitrarily long. Drop an incomplete final cluster rather than split it. 55 static func excerpt(_ value: String, characters: Int, bytes: Int) -> String { 56 precondition(characters > 0 && bytes >= 3) 57 let prefix = Array(value.utf8.prefix(bytes + 1)) 58 let byteTruncated = prefix.count > bytes 59 var bounded: String 60 if byteTruncated { 61 var encoded = Array(prefix.prefix(bytes - 3)) 62 while String(bytes: encoded, encoding: .utf8) == nil { 63 encoded.removeLast() 64 } 65 bounded = String(bytes: encoded, encoding: .utf8) ?? "" 66 if !bounded.isEmpty { 67 bounded.removeLast() 68 } 69 } else { 70 bounded = value 71 } 72 let clusterPrefix = bounded.prefix(characters + 1) 73 let truncated = byteTruncated || clusterPrefix.count > characters 74 return truncated ? String(clusterPrefix.prefix(characters - 1)) + "…" : String(clusterPrefix) 75 } 76 }