perf: reduce chat list and message handling latency

This commit is contained in:
liu weikai
2026-03-27 17:35:47 +08:00
parent 988827a3bc
commit df76526b5d
14 changed files with 376 additions and 45 deletions
@@ -344,6 +344,44 @@ void ChatConversationScreen::scrollToBottom()
}
}
bool ChatConversationScreen::updateMessageStatus(const chat::MessageId msg_id,
const chat::MessageStatus status)
{
if (!guard_ || !guard_->alive || msg_id == 0)
{
return false;
}
for (auto& item : messages_)
{
if (item.msg.msg_id != msg_id)
{
continue;
}
item.msg.status = status;
if (!item.status_label)
{
return true;
}
if (status == MessageStatus::Failed)
{
lv_label_set_text(item.status_label, "Failed");
::ui::fonts::apply_ui_chrome_font(item.status_label);
lv_obj_clear_flag(item.status_label, LV_OBJ_FLAG_HIDDEN);
}
else
{
lv_label_set_text(item.status_label, "");
::ui::fonts::apply_ui_chrome_font(item.status_label);
lv_obj_add_flag(item.status_label, LV_OBJ_FLAG_HIDDEN);
}
return true;
}
return false;
}
void ChatConversationScreen::setActionCallback(void (*cb)(ActionIntent intent, void*), void* user_data)
{
if (!guard_ || !guard_->alive)
@@ -156,6 +156,23 @@ static bool conversation_list_equal(const std::vector<chat::ConversationMeta>& l
return true;
}
static bool conversation_identity_list_equal(const std::vector<chat::ConversationMeta>& lhs,
const std::vector<chat::ConversationMeta>& rhs)
{
if (lhs.size() != rhs.size())
{
return false;
}
for (size_t index = 0; index < lhs.size(); ++index)
{
if (!(lhs[index].id == rhs[index].id))
{
return false;
}
}
return true;
}
static const char* touch_event_name(lv_event_code_t code)
{
switch (code)
@@ -337,6 +354,7 @@ void ChatMessageListScreen::setConversations(const std::vector<chat::Conversatio
team_visibility_changed ? 1 : 0,
(unsigned)convs.size());
const std::vector<chat::ConversationMeta> previous_convs = convs_;
convs_ = convs;
if (team_btn_)
{
@@ -354,7 +372,11 @@ void ChatMessageListScreen::setConversations(const std::vector<chat::Conversatio
filter_mode_ = FilterMode::Broadcast;
}
updateFilterHighlight();
rebuildList();
if (!conversation_identity_list_equal(previous_convs, convs_) ||
!updateListInPlace(convs_))
{
rebuildList();
}
}
void ChatMessageListScreen::setSelected(int index)
@@ -617,6 +639,95 @@ void ChatMessageListScreen::rebuildList()
chat::ui::message_list::input::on_ui_refreshed(&input_controller_);
}
bool ChatMessageListScreen::updateListInPlace(const std::vector<chat::ConversationMeta>& convs)
{
if (!guard_ || !guard_->alive || !list_panel_ || !lv_obj_is_valid(list_panel_))
{
return false;
}
if (!conversation_identity_list_equal(convs_, convs))
{
return false;
}
std::vector<chat::ConversationMeta> filtered;
filtered.reserve(convs.size());
for (const auto& conv : convs)
{
if (is_team_conversation(conv.id))
{
if (filter_mode_ == FilterMode::Team)
{
filtered.push_back(conv);
}
continue;
}
if (filter_mode_ == FilterMode::Direct && conv.id.peer != 0)
{
filtered.push_back(conv);
}
else if (filter_mode_ == FilterMode::Broadcast && conv.id.peer == 0)
{
filtered.push_back(conv);
}
}
if (filtered.size() != items_.size())
{
return false;
}
for (size_t index = 0; index < filtered.size(); ++index)
{
if (!(items_[index].conv == filtered[index].id))
{
return false;
}
}
for (size_t index = 0; index < filtered.size(); ++index)
{
updateListItem(index, filtered[index]);
}
return true;
}
void ChatMessageListScreen::updateListItem(const size_t index,
const chat::ConversationMeta& conv)
{
if (index >= items_.size())
{
return;
}
MessageItem& item = items_[index];
std::string title = "[" + std::string(protocol_short_label(conv.id.protocol)) + "] " + conv.name;
lv_label_set_text(item.name_label, title.c_str());
::ui::fonts::apply_chat_content_font(item.name_label, title.c_str());
std::string preview = truncate_preview(conv.preview);
lv_label_set_text(item.preview_label, preview.c_str());
::ui::fonts::apply_chat_content_font(item.preview_label, preview.c_str());
char time_buf[16];
format_time_hhmm(time_buf, conv.last_timestamp);
lv_label_set_text(item.time_label, time_buf);
::ui::fonts::apply_ui_chrome_font(item.time_label);
if (conv.unread > 0)
{
char unread_str[16];
snprintf(unread_str, sizeof(unread_str), "%d", conv.unread);
lv_label_set_text(item.unread_label, unread_str);
}
else
{
lv_label_set_text(item.unread_label, "");
}
::ui::fonts::apply_ui_chrome_font(item.unread_label);
item.unread_count = conv.unread;
}
void ChatMessageListScreen::item_event_cb(lv_event_t* e)
{
auto* screen =
@@ -333,12 +333,10 @@ void UiController::update()
{
// Process incoming messages
service_.processIncoming();
service_.flushStore();
// Refresh UI if needed
if (state_ == State::ChannelList && channel_list_)
{
refreshUnreadCounts();
}
// Refresh UI only when an event marks the conversation list dirty.
refreshUnreadCounts(false);
}
void UiController::onChannelClicked(chat::ConversationId conv)
@@ -416,19 +414,25 @@ void UiController::onChatEvent(sys::Event* event)
// Note: Haptic feedback is now handled by the app runtime event pump
// No need to call vibrator() here
if (state_ == State::Conversation &&
(uint8_t)current_channel_ == msg_event->channel)
const ChatMessage* latest = service_.getMessage(msg_event->msg_id);
if (latest)
{
CHAT_UI_LOG("[UiController::onChatEvent] Updating conversation UI...\n");
auto messages = service_.getRecentMessages(current_conv_, 50);
conversation_->clearMessages();
for (const auto& m : messages)
const bool is_current_conversation =
(state_ == State::Conversation) && (current_conv_ == chat::ConversationId(latest->channel,
latest->peer,
latest->protocol));
updateConversationMetaForMessage(*latest, !is_current_conversation);
if (is_current_conversation)
{
conversation_->addMessage(m);
(void)updateConversationViewForIncoming(*latest);
service_.markConversationRead(current_conv_);
}
else
{
conversation_list_dirty_ = true;
}
conversation_->scrollToBottom();
}
refreshUnreadCounts();
refreshUnreadCounts(false);
break;
}
@@ -437,13 +441,17 @@ void UiController::onChatEvent(sys::Event* event)
sys::ChatSendResultEvent* result_event = (sys::ChatSendResultEvent*)event;
if (state_ == State::Conversation && conversation_)
{
auto messages = service_.getRecentMessages(current_conv_, 50);
conversation_->clearMessages();
for (const auto& m : messages)
const ChatMessage* msg = service_.getMessage(result_event->msg_id);
if (!msg || !conversation_->updateMessageStatus(result_event->msg_id, msg->status))
{
conversation_->addMessage(m);
auto messages = service_.getRecentMessages(current_conv_, 50);
conversation_->clearMessages();
for (const auto& m : messages)
{
conversation_->addMessage(m);
}
conversation_->scrollToBottom();
}
conversation_->scrollToBottom();
}
(void)result_event;
break;
@@ -451,7 +459,8 @@ void UiController::onChatEvent(sys::Event* event)
case sys::EventType::ChatUnreadChanged:
{
refreshUnreadCounts();
conversation_list_dirty_ = true;
refreshUnreadCounts(false);
break;
}
@@ -519,7 +528,7 @@ void UiController::switchToChannelList()
}
service_.setModelEnabled(true);
refreshUnreadCounts();
refreshUnreadCounts(true);
}
void UiController::switchToConversation(chat::ConversationId conv)
@@ -767,28 +776,29 @@ void UiController::handleSendMessage(const std::string& text)
}
void UiController::refreshUnreadCounts()
{
refreshUnreadCounts(true);
}
void UiController::refreshUnreadCounts(const bool force_reload)
{
if (!channel_list_)
{
return;
}
size_t total = 0;
auto convs = service_.getConversations(0, 0, &total);
// Update conversation names with contact nicknames
for (auto& conv : convs)
if (force_reload || conversation_list_dirty_ || cached_conversations_.empty())
{
if (conv.id.peer != 0)
{
std::string contact_name = app::messagingFacade().getContactService().getContactName(conv.id.peer);
if (!contact_name.empty())
{
conv.name = contact_name;
}
// Otherwise keep the short_name from ConversationMeta
}
syncConversationListFromStore();
}
applyConversationListToUi();
}
void UiController::syncConversationListFromStore()
{
size_t total = 0;
cached_conversations_ = service_.getConversations(0, 0, &total);
normalizeConversationNames(cached_conversations_);
team::ui::TeamUiSnapshot team_snap;
if (team::ui::team_ui_get_store().load(team_snap) && team_snap.has_team_id)
@@ -811,16 +821,97 @@ void UiController::refreshUnreadCounts()
{
team_conv.preview = "No messages";
}
convs.insert(convs.begin(), team_conv);
cached_conversations_.insert(cached_conversations_.begin(), team_conv);
}
channel_list_->setConversations(convs);
channel_list_->setSelectedConversation(current_conv_);
conversation_list_dirty_ = false;
}
// Update header status (battery only, with icon)
void UiController::normalizeConversationNames(std::vector<chat::ConversationMeta>& convs) const
{
for (auto& conv : convs)
{
if (conv.id.peer == 0)
{
continue;
}
std::string contact_name = app::messagingFacade().getContactService().getContactName(conv.id.peer);
if (!contact_name.empty())
{
conv.name = contact_name;
}
}
}
void UiController::applyConversationListToUi()
{
if (!channel_list_)
{
return;
}
channel_list_->setConversations(cached_conversations_);
channel_list_->setSelectedConversation(current_conv_);
channel_list_->updateBatteryFromBoard();
}
void UiController::updateConversationMetaForMessage(const chat::ChatMessage& msg,
const bool increment_unread)
{
if (isTeamConversation(chat::ConversationId(msg.channel, msg.peer, msg.protocol)))
{
conversation_list_dirty_ = true;
return;
}
chat::ConversationMeta meta;
meta.id = chat::ConversationId(msg.channel, msg.peer, msg.protocol);
meta.name = (msg.peer == 0) ? "Broadcast" : resolve_contact_name(msg.peer);
meta.preview = msg.text;
meta.last_timestamp = msg.timestamp;
meta.unread = (increment_unread && msg.status == chat::MessageStatus::Incoming) ? 1 : 0;
bool found = false;
for (auto it = cached_conversations_.begin(); it != cached_conversations_.end(); ++it)
{
if (!(it->id == meta.id))
{
continue;
}
found = true;
meta.unread += it->unread;
if (!increment_unread && msg.status == chat::MessageStatus::Incoming)
{
meta.unread = 0;
}
cached_conversations_.erase(it);
break;
}
if (!found && msg.peer == 0)
{
meta.name = "Broadcast";
}
cached_conversations_.insert(cached_conversations_.begin(), meta);
}
bool UiController::updateConversationViewForIncoming(const chat::ChatMessage& msg)
{
if (!conversation_)
{
return false;
}
if (!(current_conv_ == chat::ConversationId(msg.channel, msg.peer, msg.protocol)))
{
return false;
}
conversation_->addMessage(msg);
return true;
}
bool UiController::isTeamConversation(const chat::ConversationId& conv) const
{
return isTeamConversationId(conv);