Telegram-style date pills в чат-листе — sticky headers с push-переходом между секциями

This commit is contained in:
2026-03-31 02:48:28 +05:00
parent 3fc15c14ff
commit 6b55baacd8
10 changed files with 408 additions and 42 deletions

View File

@@ -154,7 +154,13 @@ private extension ChatRowView {
if isTyping && !dialog.isSavedMessages {
return "typing..."
}
if dialog.lastMessage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let raw = dialog.lastMessage.trimmingCharacters(in: .whitespacesAndNewlines)
if raw.isEmpty {
return "No messages yet"
}
// Safety net: never show encrypted ciphertext (ivBase64:ctBase64) to user.
// This catches stale data persisted before isGarbageText was improved.
if Self.looksLikeCiphertext(raw) {
return "No messages yet"
}
if let cached = Self.messageTextCache[dialog.lastMessage] {
@@ -170,6 +176,27 @@ private extension ChatRowView {
Self.messageTextCache[dialog.lastMessage] = result
return result
}
/// Detects encrypted payload formats that should never be shown in UI.
private static func looksLikeCiphertext(_ text: String) -> Bool {
// CHNK: chunked format
if text.hasPrefix("CHNK:") { return true }
// ivBase64:ctBase64 or hex-encoded XChaCha20 ciphertext
let parts = text.components(separatedBy: ":")
if parts.count == 2 {
let base64Chars = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "+/="))
let bothBase64 = parts.allSatisfy { part in
part.count >= 16 && part.unicodeScalars.allSatisfy { base64Chars.contains($0) }
}
if bothBase64 { return true }
}
// Pure hex string (40 chars, only hex digits) XChaCha20 wire format
if text.count >= 40 {
let hexChars = CharacterSet(charactersIn: "0123456789abcdefABCDEF")
if text.unicodeScalars.allSatisfy({ hexChars.contains($0) }) { return true }
}
return false
}
}
// MARK: - Trailing Column