RosettaMessageListController минимальный контроллер для кастомного ListView

This commit is contained in:
2026-04-18 12:44:00 +05:00
parent 2f80ab5cc1
commit 2430e95987

View File

@@ -0,0 +1,131 @@
import UIKit
import os
// MARK: - RosettaMessageListController
/// Chat message list using custom RosettaListView (Telegram-parity).
/// Replaces NativeMessageListController (UICollectionView-based).
///
/// Phase 1: Basic message display + scroll. No keyboard, date pills, selection.
@MainActor
final class RosettaMessageListController: UIViewController {
private static let perfLog = Logger(subsystem: "com.rosetta.messenger", category: "RosettaList")
// MARK: - Configuration
struct Config {
var maxBubbleWidth: CGFloat
var currentPublicKey: String
var opponentPublicKey: String
var opponentTitle: String
var isGroupChat: Bool
var groupAdminKey: String
var actions: MessageCellActions
}
private let config: Config
// MARK: - Views
private let listView = RosettaListView(frame: .zero)
// MARK: - State
private var messages: [ChatMessage] = []
private var layoutCache: [String: MessageCellLayout] = [:]
private var textLayoutCache: [String: CoreTextTextLayout] = [:]
// MARK: - Init
init(config: Config) {
self.config = config
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
listView.frame = view.bounds
listView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
listView.stackFromBottom = true
view.addSubview(listView)
}
// MARK: - Public API
/// Update displayed messages. Calculates layouts and builds list items.
func update(messages: [ChatMessage]) {
let start = CACurrentMediaTime()
self.messages = messages
// Calculate layouts for new messages (reuse cached)
calculateLayouts()
// Build list items
var items: [RosettaListItem] = []
for message in messages {
guard let layout = layoutCache[message.id] else { continue }
let textLayout = textLayoutCache[message.id]
let item = ChatMessageListItem(
message: message,
layout: layout,
textLayout: textLayout,
timestampText: formatTimestamp(message.timestamp),
actions: config.actions
)
items.append(item)
}
listView.setItems(items)
let ms = (CACurrentMediaTime() - start) * 1000
Self.perfLog.notice("⚡ RosettaList.update: \(messages.count, privacy: .public) msgs in \(String(format: "%.0f", ms), privacy: .public)ms")
}
/// Scroll to newest message.
func scrollToBottom(animated: Bool) {
listView.scrollToBottom(animated: animated)
}
// MARK: - Layout Calculation
private func calculateLayouts() {
let isDark = traitCollection.userInterfaceStyle == .dark
let (layouts, textLayouts) = MessageCellLayout.batchCalculate(
messages: messages,
maxBubbleWidth: config.maxBubbleWidth,
currentPublicKey: config.currentPublicKey,
opponentPublicKey: config.opponentPublicKey,
opponentTitle: config.opponentTitle,
isGroupChat: config.isGroupChat,
groupAdminKey: config.groupAdminKey,
isDarkMode: isDark,
dirtyIds: nil,
existingLayouts: layoutCache.isEmpty ? nil : layoutCache,
existingTextLayouts: textLayoutCache.isEmpty ? nil : textLayoutCache
)
layoutCache = layouts
textLayoutCache = textLayouts
}
// MARK: - Timestamp Formatting
private static let timestampFormatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "HH:mm"
f.locale = .autoupdatingCurrent
f.timeZone = .autoupdatingCurrent
return f
}()
private func formatTimestamp(_ ms: Int64) -> String {
Self.timestampFormatter.string(from: Date(timeIntervalSince1970: Double(ms) / 1000))
}
}