mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-08-21 18:19:43 +00:00
fix(pager): preserve voice hold and add chat selection
This commit is contained in:
@@ -52,9 +52,14 @@ class ChatConversationScreen
|
||||
~ChatConversationScreen();
|
||||
|
||||
void addMessage(const ::ui::chat::MessageRow& row);
|
||||
/** @brief Adds one local-only, click-to-play VMP voice bubble. */
|
||||
/** @brief Adds one local-only, selectable VMP voice bubble. */
|
||||
void addVoiceMessage(const ::ui::chat_voice::MessageSummary& summary);
|
||||
void clearMessages();
|
||||
/** Moves the non-touch timeline selection without changing rotary scroll. */
|
||||
bool selectPreviousMessage();
|
||||
bool selectNextMessage();
|
||||
/** Plays the selected voice bubble; selected non-voice bubbles consume Enter. */
|
||||
bool activateSelectedMessage();
|
||||
void scrollToTop();
|
||||
void scrollToBottom();
|
||||
bool updateMessageStatus(
|
||||
@@ -224,8 +229,11 @@ class ChatConversationScreen
|
||||
bool history_newer_boundary_notified_ = false;
|
||||
bool location_map_visible_ = false;
|
||||
bool location_map_created_ = false;
|
||||
int selected_message_index_ = -1;
|
||||
|
||||
void createMessageItem(const ::ui::chat::MessageRow& row);
|
||||
bool selectMessageRelative(int direction);
|
||||
void setSelectedMessageIndex(int index);
|
||||
void handleScroll();
|
||||
void enableRetryAction(MessageItem& item);
|
||||
void disableRetryAction(MessageItem& item);
|
||||
|
||||
@@ -304,6 +304,8 @@ void ChatComposeScreen::setVoiceButton(const char* label, bool visible)
|
||||
{
|
||||
if (!impl_ || !impl_->w.position_btn) return;
|
||||
impl_->auxiliary_ctx.intent = ActionIntent::VoiceStart;
|
||||
const bool visibility_changed =
|
||||
lv_obj_has_flag(impl_->w.position_btn, LV_OBJ_FLAG_HIDDEN) == visible;
|
||||
if (label)
|
||||
{
|
||||
set_btn_label_text(impl_->w.position_btn, label);
|
||||
@@ -318,7 +320,16 @@ void ChatComposeScreen::setVoiceButton(const char* label, bool visible)
|
||||
lv_obj_add_flag(impl_->w.position_btn, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
syncFocusOrder();
|
||||
// Recording begins on LV_EVENT_PRESSED and ends on LV_EVENT_RELEASED.
|
||||
// Rebuilding the LVGL group just to refresh "Voice" -> "Release" removes
|
||||
// the currently pressed object from the group, which transfers encoder
|
||||
// focus before the release reaches it. Only a visibility transition
|
||||
// changes the group membership; a label/timer refresh must preserve the
|
||||
// focused Voice button for the entire press-and-hold sequence.
|
||||
if (visibility_changed)
|
||||
{
|
||||
syncFocusOrder();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ChatComposeScreen::getText() const
|
||||
|
||||
@@ -537,7 +537,7 @@ void format_voice_text(char* out,
|
||||
std::snprintf(out,
|
||||
out_size,
|
||||
"%s",
|
||||
playing ? "Playing voice..." : "Voice message - tap to play");
|
||||
playing ? "Playing voice..." : "Voice message - Enter plays");
|
||||
return;
|
||||
}
|
||||
std::snprintf(out,
|
||||
@@ -545,7 +545,7 @@ void format_voice_text(char* out,
|
||||
"Voice %lu.%lus%s",
|
||||
static_cast<unsigned long>(tenths / 10U),
|
||||
static_cast<unsigned long>(tenths % 10U),
|
||||
playing ? " - playing" : " - tap to play");
|
||||
playing ? " - playing" : " - Enter plays");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -818,6 +818,14 @@ void ChatConversationScreen::addMessage(const ::ui::chat::MessageRow& row)
|
||||
}
|
||||
if (messages_.size() >= MAX_DISPLAY_MESSAGES)
|
||||
{
|
||||
if (selected_message_index_ == 0)
|
||||
{
|
||||
setSelectedMessageIndex(-1);
|
||||
}
|
||||
else if (selected_message_index_ > 0)
|
||||
{
|
||||
--selected_message_index_;
|
||||
}
|
||||
MessageItem& oldest = messages_[0];
|
||||
if (oldest.container)
|
||||
{
|
||||
@@ -845,6 +853,14 @@ void ChatConversationScreen::addVoiceMessage(
|
||||
}
|
||||
if (messages_.size() >= MAX_DISPLAY_MESSAGES)
|
||||
{
|
||||
if (selected_message_index_ == 0)
|
||||
{
|
||||
setSelectedMessageIndex(-1);
|
||||
}
|
||||
else if (selected_message_index_ > 0)
|
||||
{
|
||||
--selected_message_index_;
|
||||
}
|
||||
MessageItem& oldest = messages_[0];
|
||||
if (oldest.container)
|
||||
{
|
||||
@@ -951,6 +967,7 @@ void ChatConversationScreen::clearMessages()
|
||||
return;
|
||||
}
|
||||
clear_timers(TimerDomain::VoicePlayback);
|
||||
setSelectedMessageIndex(-1);
|
||||
size_t index = 0;
|
||||
for (auto& item : messages_)
|
||||
{
|
||||
@@ -968,6 +985,92 @@ void ChatConversationScreen::clearMessages()
|
||||
static_cast<unsigned long>(lv_tick_elaps(started_ms)));
|
||||
}
|
||||
|
||||
bool ChatConversationScreen::selectPreviousMessage()
|
||||
{
|
||||
return selectMessageRelative(-1);
|
||||
}
|
||||
|
||||
bool ChatConversationScreen::selectNextMessage()
|
||||
{
|
||||
return selectMessageRelative(1);
|
||||
}
|
||||
|
||||
bool ChatConversationScreen::activateSelectedMessage()
|
||||
{
|
||||
if (selected_message_index_ < 0 ||
|
||||
selected_message_index_ >= static_cast<int>(messages_.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MessageItem& item = messages_[static_cast<size_t>(selected_message_index_)];
|
||||
if (item.voice_playback_ctx && item.bubble && lv_obj_is_valid(item.bubble))
|
||||
{
|
||||
lv_obj_send_event(item.bubble, LV_EVENT_CLICKED, nullptr);
|
||||
}
|
||||
|
||||
// A selected text, image, or location bubble deliberately consumes Enter.
|
||||
// This reserves the key for an eventual type-specific bubble action and
|
||||
// prevents the message-list's edit mode from changing unexpectedly.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatConversationScreen::selectMessageRelative(int direction)
|
||||
{
|
||||
if (direction == 0 || messages_.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int newest = static_cast<int>(messages_.size()) - 1;
|
||||
int next = selected_message_index_;
|
||||
if (next < 0 || next > newest)
|
||||
{
|
||||
next = newest;
|
||||
}
|
||||
else
|
||||
{
|
||||
next += direction;
|
||||
if (next < 0 || next > newest)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedMessageIndex(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChatConversationScreen::setSelectedMessageIndex(int index)
|
||||
{
|
||||
if (selected_message_index_ >= 0 &&
|
||||
selected_message_index_ < static_cast<int>(messages_.size()))
|
||||
{
|
||||
MessageItem& previous = messages_[static_cast<size_t>(selected_message_index_)];
|
||||
if (previous.bubble && lv_obj_is_valid(previous.bubble))
|
||||
{
|
||||
lv_obj_clear_state(previous.bubble, LV_STATE_USER_1);
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0 || index >= static_cast<int>(messages_.size()))
|
||||
{
|
||||
selected_message_index_ = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
selected_message_index_ = index;
|
||||
MessageItem& selected = messages_[static_cast<size_t>(selected_message_index_)];
|
||||
if (selected.bubble && lv_obj_is_valid(selected.bubble))
|
||||
{
|
||||
lv_obj_add_state(selected.bubble, LV_STATE_USER_1);
|
||||
}
|
||||
if (selected.container && lv_obj_is_valid(selected.container))
|
||||
{
|
||||
lv_obj_scroll_to_view(selected.container, LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatConversationScreen::voice_message_event_cb(lv_event_t* e)
|
||||
{
|
||||
if (!e || lv_event_get_code(e) != LV_EVENT_CLICKED)
|
||||
@@ -1230,7 +1333,9 @@ void ChatConversationScreen::toggleShortcutHelp()
|
||||
}
|
||||
|
||||
static constexpr ::ui::components::shortcut_help_modal::Row rows[] = {
|
||||
{"S", nullptr, "Compose message"},
|
||||
{"R", nullptr, "Compose message"},
|
||||
{"W/S", nullptr, "Select message"},
|
||||
{"Enter", nullptr, "Play selected voice"},
|
||||
{"Up/Down", nullptr, "Scroll messages"},
|
||||
{"Prev/Next", nullptr, "Load older/newer"},
|
||||
{"Home/End", nullptr, "Top/bottom"},
|
||||
@@ -1252,7 +1357,7 @@ void ChatConversationScreen::toggleShortcutHelp()
|
||||
config.rows = rows;
|
||||
config.row_count = sizeof(rows) / sizeof(rows[0]);
|
||||
config.width = ::ui::page_profile::is_dense() ? 288 : 304;
|
||||
config.height = ::ui::page_profile::is_dense() ? 170 : 186;
|
||||
config.height = ::ui::page_profile::is_dense() ? 192 : 208;
|
||||
config.restore_group = lv_group_get_default();
|
||||
(void)::ui::components::shortcut_help_modal::open(
|
||||
shortcut_help_modal_,
|
||||
|
||||
@@ -143,10 +143,31 @@ static bool handle_conversation_shortcut(ChatConversationScreen* screen,
|
||||
consume(e);
|
||||
return true;
|
||||
}
|
||||
if (key == 's' || key == 'S')
|
||||
if (key == 'r' || key == 'R')
|
||||
{
|
||||
return send_reply(screen, e);
|
||||
}
|
||||
if (key == 'w' || key == 'W')
|
||||
{
|
||||
if (screen->selectPreviousMessage())
|
||||
{
|
||||
consume(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (key == 's' || key == 'S')
|
||||
{
|
||||
if (screen->selectNextMessage())
|
||||
{
|
||||
consume(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (key == LV_KEY_ENTER && screen->activateSelectedMessage())
|
||||
{
|
||||
consume(e);
|
||||
return true;
|
||||
}
|
||||
if (handle_map_key(screen, e, key))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -22,6 +22,7 @@ static lv_style_t s_bubble_base;
|
||||
static lv_style_t s_bubble_self;
|
||||
static lv_style_t s_bubble_other;
|
||||
static lv_style_t s_bubble_unverified;
|
||||
static lv_style_t s_bubble_selected;
|
||||
static lv_style_t s_bubble_text;
|
||||
static lv_style_t s_bubble_time;
|
||||
|
||||
@@ -128,6 +129,15 @@ void init_once()
|
||||
lv_style_set_bg_color(&s_bubble_unverified, lv_color_hex(0xF4E1DE));
|
||||
lv_style_set_border_color(&s_bubble_unverified, lv_color_hex(0xC47D70));
|
||||
|
||||
// This style is shared by every bubble. W/S selection only toggles the
|
||||
// LV_STATE_USER_1 bit; it does not add bubbles to an LVGL group or create
|
||||
// any per-message focus bookkeeping/allocation.
|
||||
lv_style_init(&s_bubble_selected);
|
||||
lv_style_set_outline_width(&s_bubble_selected, dense ? 1 : 2);
|
||||
lv_style_set_outline_pad(&s_bubble_selected, dense ? 1 : 2);
|
||||
lv_style_set_outline_color(&s_bubble_selected, lv_color_hex(0xE57B1F));
|
||||
lv_style_set_outline_opa(&s_bubble_selected, LV_OPA_COVER);
|
||||
|
||||
lv_style_init(&s_bubble_text);
|
||||
lv_style_set_text_color(&s_bubble_text, kTextColor);
|
||||
lv_style_set_text_align(&s_bubble_text, LV_TEXT_ALIGN_LEFT);
|
||||
@@ -184,6 +194,7 @@ void apply_bubble(lv_obj_t* bubble, bool is_self, bool source_unverified)
|
||||
is_self ? &s_bubble_self
|
||||
: (source_unverified ? &s_bubble_unverified : &s_bubble_other);
|
||||
lv_obj_add_style(bubble, message_style, LV_PART_MAIN);
|
||||
lv_obj_add_style(bubble, &s_bubble_selected, LV_PART_MAIN | LV_STATE_USER_1);
|
||||
}
|
||||
|
||||
void apply_bubble_text(lv_obj_t* label)
|
||||
|
||||
Reference in New Issue
Block a user