mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-08-28 23:00:19 +00:00
Improve Nomad network directory UX
This commit is contained in:
@@ -129,6 +129,7 @@ struct NetworkPageState
|
||||
DirectoryMode directory_mode = DirectoryMode::Announces;
|
||||
bool immersive = false;
|
||||
bool directory_collapsed = false;
|
||||
bool browser_collapsed = false;
|
||||
bool suppress_history = false;
|
||||
rtdir::Status announce_status{};
|
||||
rtdir::Status address_status{};
|
||||
@@ -564,6 +565,7 @@ void rebuild_focus_group(lv_obj_t* preferred = nullptr);
|
||||
void focus_browser_viewport();
|
||||
void focus_directory_panel();
|
||||
void apply_layout_state();
|
||||
lv_coord_t resolve_directory_width();
|
||||
void open_network_help_modal();
|
||||
void close_network_help_modal();
|
||||
|
||||
@@ -650,6 +652,10 @@ void add_viewport_links_to_group()
|
||||
|
||||
void add_browser_focusables()
|
||||
{
|
||||
if (g_state.browser_collapsed && !g_state.immersive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!g_state.immersive)
|
||||
{
|
||||
add_visible_to_group(g_state.browser_back_btn);
|
||||
@@ -1550,6 +1556,7 @@ void apply_layout_state()
|
||||
{
|
||||
const bool immersive = g_state.immersive;
|
||||
const bool hide_directory = immersive || g_state.directory_collapsed;
|
||||
const bool hide_browser = !immersive && g_state.browser_collapsed;
|
||||
if (g_state.header)
|
||||
{
|
||||
immersive ? lv_obj_add_flag(g_state.header, LV_OBJ_FLAG_HIDDEN)
|
||||
@@ -1559,6 +1566,14 @@ void apply_layout_state()
|
||||
{
|
||||
hide_directory ? lv_obj_add_flag(g_state.directory_panel, LV_OBJ_FLAG_HIDDEN)
|
||||
: lv_obj_clear_flag(g_state.directory_panel, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_set_width(g_state.directory_panel,
|
||||
hide_browser ? LV_PCT(100) : resolve_directory_width());
|
||||
lv_obj_set_flex_grow(g_state.directory_panel, hide_browser ? 1 : 0);
|
||||
}
|
||||
if (g_state.browser_panel)
|
||||
{
|
||||
hide_browser ? lv_obj_add_flag(g_state.browser_panel, LV_OBJ_FLAG_HIDDEN)
|
||||
: lv_obj_clear_flag(g_state.browser_panel, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
if (g_state.browser_toolbar)
|
||||
{
|
||||
@@ -1571,7 +1586,9 @@ void apply_layout_state()
|
||||
lv_obj_set_style_pad_right(g_state.content, immersive ? 0 : 2, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_top(g_state.content, immersive ? 0 : 2, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_bottom(g_state.content, immersive ? 0 : 2, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_column(g_state.content, hide_directory ? 0 : 4, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_column(g_state.content,
|
||||
(hide_directory || hide_browser) ? 0 : 4,
|
||||
LV_PART_MAIN);
|
||||
}
|
||||
if (g_state.browser_panel)
|
||||
{
|
||||
@@ -1583,12 +1600,12 @@ void apply_layout_state()
|
||||
{
|
||||
lv_obj_set_style_radius(g_state.viewport, immersive ? 0 : 8, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(g_state.viewport, immersive ? 0 : 1, LV_PART_MAIN);
|
||||
if (app_g)
|
||||
if (app_g && !hide_browser)
|
||||
{
|
||||
lv_group_focus_obj(g_state.viewport);
|
||||
}
|
||||
}
|
||||
rebuild_focus_group(g_state.viewport);
|
||||
rebuild_focus_group(hide_browser ? g_state.directory_list : g_state.viewport);
|
||||
}
|
||||
|
||||
void toggle_immersive()
|
||||
@@ -1603,7 +1620,12 @@ void toggle_directory_panel()
|
||||
{
|
||||
g_state.immersive = false;
|
||||
}
|
||||
g_state.directory_collapsed = !g_state.directory_collapsed;
|
||||
const bool hide_directory = !g_state.directory_collapsed;
|
||||
g_state.directory_collapsed = hide_directory;
|
||||
if (hide_directory)
|
||||
{
|
||||
g_state.browser_collapsed = false;
|
||||
}
|
||||
apply_layout_state();
|
||||
::ui::feedback::show_notice(g_state.directory_collapsed ? safe_tr("Directory hidden")
|
||||
: safe_tr("Directory shown"),
|
||||
@@ -1618,6 +1640,32 @@ void toggle_directory_panel()
|
||||
}
|
||||
}
|
||||
|
||||
void toggle_browser_panel()
|
||||
{
|
||||
if (g_state.immersive)
|
||||
{
|
||||
g_state.immersive = false;
|
||||
}
|
||||
const bool hide_browser = !g_state.browser_collapsed;
|
||||
g_state.browser_collapsed = hide_browser;
|
||||
if (hide_browser)
|
||||
{
|
||||
g_state.directory_collapsed = false;
|
||||
}
|
||||
apply_layout_state();
|
||||
::ui::feedback::show_notice(g_state.browser_collapsed ? safe_tr("Browser hidden")
|
||||
: safe_tr("Browser shown"),
|
||||
1200);
|
||||
if (g_state.browser_collapsed)
|
||||
{
|
||||
focus_directory_panel();
|
||||
}
|
||||
else
|
||||
{
|
||||
focus_browser_viewport();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_help_shortcut_key(uint32_t key)
|
||||
{
|
||||
return key == 'h' || key == 'H';
|
||||
@@ -1628,6 +1676,11 @@ bool is_directory_toggle_shortcut_key(uint32_t key)
|
||||
return key == 'c' || key == 'C';
|
||||
}
|
||||
|
||||
bool is_browser_toggle_shortcut_key(uint32_t key)
|
||||
{
|
||||
return key == 'b' || key == 'B';
|
||||
}
|
||||
|
||||
void close_network_help_modal()
|
||||
{
|
||||
::ui::components::shortcut_help_modal::close(g_state.help_modal);
|
||||
@@ -1645,10 +1698,11 @@ void open_network_help_modal()
|
||||
return;
|
||||
}
|
||||
|
||||
::ui::components::shortcut_help_modal::Row rows[10] = {};
|
||||
::ui::components::shortcut_help_modal::Row rows[11] = {};
|
||||
std::size_t row_count = 0;
|
||||
rows[row_count++] = {"S", "/", "Search announces"};
|
||||
rows[row_count++] = {"C", nullptr, "Show or hide left column"};
|
||||
rows[row_count++] = {"B", nullptr, "Show or hide browser"};
|
||||
rows[row_count++] = {"I", nullptr, "Immersive browser"};
|
||||
rows[row_count++] = {"Left", "Right", "Switch pane focus"};
|
||||
rows[row_count++] = {"Enter", nullptr, "Open selected item"};
|
||||
@@ -1691,15 +1745,30 @@ void page_shortcut_event_cb(lv_event_t* event)
|
||||
lv_event_stop_processing(event);
|
||||
return;
|
||||
}
|
||||
if (is_browser_toggle_shortcut_key(key))
|
||||
{
|
||||
toggle_browser_panel();
|
||||
lv_event_stop_processing(event);
|
||||
return;
|
||||
}
|
||||
if (key == LV_KEY_RIGHT && object_in_subtree(g_state.directory_panel, target))
|
||||
{
|
||||
if (g_state.browser_collapsed)
|
||||
{
|
||||
g_state.browser_collapsed = false;
|
||||
apply_layout_state();
|
||||
}
|
||||
focus_browser_viewport();
|
||||
lv_event_stop_processing(event);
|
||||
return;
|
||||
}
|
||||
if (key == LV_KEY_LEFT && object_in_subtree(g_state.browser_panel, target) &&
|
||||
!g_state.directory_collapsed)
|
||||
if (key == LV_KEY_LEFT && object_in_subtree(g_state.browser_panel, target))
|
||||
{
|
||||
if (g_state.directory_collapsed)
|
||||
{
|
||||
g_state.directory_collapsed = false;
|
||||
apply_layout_state();
|
||||
}
|
||||
focus_directory_panel();
|
||||
lv_event_stop_processing(event);
|
||||
return;
|
||||
|
||||
@@ -464,6 +464,48 @@ void copyCString(char* out, size_t out_len, const char* in)
|
||||
out[out_len - 1] = '\0';
|
||||
}
|
||||
|
||||
bool copyTextAppDataDisplayName(const uint8_t* data,
|
||||
size_t len,
|
||||
char* out,
|
||||
size_t out_len)
|
||||
{
|
||||
if (!data || len == 0 || len > 96 || !out || out_len == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t used = 0;
|
||||
bool has_visible = false;
|
||||
for (size_t index = 0; index < len; ++index)
|
||||
{
|
||||
uint8_t byte = data[index];
|
||||
if (byte == '\t' || byte == '\r' || byte == '\n')
|
||||
{
|
||||
byte = ' ';
|
||||
}
|
||||
else if (byte == 0 || byte < 0x20 || byte == 0x7F)
|
||||
{
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (used + 1U < out_len)
|
||||
{
|
||||
out[used++] = static_cast<char>(byte);
|
||||
}
|
||||
if (byte != ' ')
|
||||
{
|
||||
has_visible = true;
|
||||
}
|
||||
}
|
||||
while (used != 0 && out[used - 1U] == ' ')
|
||||
{
|
||||
--used;
|
||||
}
|
||||
out[used] = '\0';
|
||||
return has_visible && used != 0;
|
||||
}
|
||||
|
||||
uint32_t fnv1a32(const uint8_t* data, size_t len)
|
||||
{
|
||||
uint32_t hash = 2166136261UL;
|
||||
@@ -2237,6 +2279,14 @@ bool LxmfAdapter::handleAnnouncePacket(const uint8_t* raw_packet, size_t raw_len
|
||||
(void)has_stamp_cost;
|
||||
(void)stamp_cost;
|
||||
}
|
||||
else if (!(delivery_announce || propagation_announce || call_audio_announce) &&
|
||||
announce.app_data && announce.app_data_len != 0)
|
||||
{
|
||||
(void)copyTextAppDataDisplayName(announce.app_data,
|
||||
announce.app_data_len,
|
||||
announce_display_name,
|
||||
sizeof(announce_display_name));
|
||||
}
|
||||
|
||||
rtdir::AnnounceRecord directory_announce{};
|
||||
directory_announce.valid = true;
|
||||
|
||||
@@ -207,6 +207,58 @@ bool parse_hex(std::string_view text, uint8_t* out, std::size_t out_len)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool copy_text_app_data_display_name_from_hex(std::string_view text,
|
||||
char* out,
|
||||
std::size_t out_len)
|
||||
{
|
||||
if (!out || out_len == 0 || text.empty() || (text.size() % 2U) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t byte_count = text.size() / 2U;
|
||||
if (byte_count > 96)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t used = 0;
|
||||
bool has_visible = false;
|
||||
for (std::size_t index = 0; index < byte_count; ++index)
|
||||
{
|
||||
const uint8_t hi = hex_nibble(text[index * 2U]);
|
||||
const uint8_t lo = hex_nibble(text[(index * 2U) + 1U]);
|
||||
if (hi == 0xFF || lo == 0xFF)
|
||||
{
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
uint8_t byte = static_cast<uint8_t>((hi << 4U) | lo);
|
||||
if (byte == '\t' || byte == '\r' || byte == '\n')
|
||||
{
|
||||
byte = ' ';
|
||||
}
|
||||
else if (byte == 0 || byte < 0x20 || byte == 0x7F)
|
||||
{
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
if (used + 1U < out_len)
|
||||
{
|
||||
out[used++] = static_cast<char>(byte);
|
||||
}
|
||||
if (byte != ' ')
|
||||
{
|
||||
has_visible = true;
|
||||
}
|
||||
}
|
||||
while (used != 0 && out[used - 1U] == ' ')
|
||||
{
|
||||
--used;
|
||||
}
|
||||
out[used] = '\0';
|
||||
return has_visible && used != 0;
|
||||
}
|
||||
|
||||
std::string sanitize_field(const char* text)
|
||||
{
|
||||
std::string out = text ? text : "";
|
||||
@@ -1063,6 +1115,12 @@ bool parse_announce_line(std::string_view line, AnnounceRecord& out)
|
||||
parsed.delivery = truthy(fields.values[9]);
|
||||
parsed.propagation = truthy(fields.values[10]);
|
||||
copy_view(parsed.display_name, sizeof(parsed.display_name), fields.values[11]);
|
||||
if (parsed.display_name[0] == '\0' && fields.count >= 14)
|
||||
{
|
||||
(void)copy_text_app_data_display_name_from_hex(fields.values[13],
|
||||
parsed.display_name,
|
||||
sizeof(parsed.display_name));
|
||||
}
|
||||
parsed.valid = true;
|
||||
out = parsed;
|
||||
return true;
|
||||
|
||||
@@ -151,6 +151,58 @@ bool parse_hex(std::string_view text, uint8_t* out, std::size_t out_len)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool copy_text_app_data_display_name_from_hex(std::string_view text,
|
||||
char* out,
|
||||
std::size_t out_len)
|
||||
{
|
||||
if (!out || out_len == 0 || text.empty() || (text.size() % 2U) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t byte_count = text.size() / 2U;
|
||||
if (byte_count > 96)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t used = 0;
|
||||
bool has_visible = false;
|
||||
for (std::size_t index = 0; index < byte_count; ++index)
|
||||
{
|
||||
const uint8_t hi = hex_nibble(text[index * 2U]);
|
||||
const uint8_t lo = hex_nibble(text[(index * 2U) + 1U]);
|
||||
if (hi == 0xFF || lo == 0xFF)
|
||||
{
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
uint8_t byte = static_cast<uint8_t>((hi << 4U) | lo);
|
||||
if (byte == '\t' || byte == '\r' || byte == '\n')
|
||||
{
|
||||
byte = ' ';
|
||||
}
|
||||
else if (byte == 0 || byte < 0x20 || byte == 0x7F)
|
||||
{
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
if (used + 1U < out_len)
|
||||
{
|
||||
out[used++] = static_cast<char>(byte);
|
||||
}
|
||||
if (byte != ' ')
|
||||
{
|
||||
has_visible = true;
|
||||
}
|
||||
}
|
||||
while (used != 0 && out[used - 1U] == ' ')
|
||||
{
|
||||
--used;
|
||||
}
|
||||
out[used] = '\0';
|
||||
return has_visible && used != 0;
|
||||
}
|
||||
|
||||
std::string sanitize_field(const char* text)
|
||||
{
|
||||
std::string out = text ? text : "";
|
||||
@@ -502,6 +554,12 @@ bool parse_announce_line(std::string_view line, AnnounceRecord& out)
|
||||
parsed.delivery = truthy(fields.values[9]);
|
||||
parsed.propagation = truthy(fields.values[10]);
|
||||
copy_view(parsed.display_name, sizeof(parsed.display_name), fields.values[11]);
|
||||
if (parsed.display_name[0] == '\0' && fields.count >= 14)
|
||||
{
|
||||
(void)copy_text_app_data_display_name_from_hex(fields.values[13],
|
||||
parsed.display_name,
|
||||
sizeof(parsed.display_name));
|
||||
}
|
||||
parsed.valid = true;
|
||||
out = parsed;
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user