mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-08-06 10:49:28 +00:00
372 lines
9.7 KiB
C++
372 lines
9.7 KiB
C++
/**
|
|
* @file ui_status.cpp
|
|
* @brief Global UI status indicators (top bar icons + menu badges)
|
|
*/
|
|
|
|
#include "ui/ui_status.h"
|
|
|
|
#include "app/app_config.h"
|
|
#include "app/app_facade_access.h"
|
|
#if !defined(TRAIL_MATE_FINAL_IDF_NO_APP_FACADE)
|
|
#include "chat/usecase/chat_service.h"
|
|
#endif
|
|
#include "platform/ui/lora_runtime.h"
|
|
#include "platform/ui/tracker_runtime.h"
|
|
#include "platform/ui/walkie_runtime.h"
|
|
#include "platform/ui/wifi_runtime.h"
|
|
#include "sys/clock.h"
|
|
#if !defined(GAT562_NO_TEAM) || !GAT562_NO_TEAM
|
|
#include "platform/ui/team_ui_snapshot_store.h"
|
|
#endif
|
|
#include "ui/presentation_sources/runtime_gps_status_source.h"
|
|
#include "ui_presentation/gps/gps_status_model.h"
|
|
|
|
#include <cstdio>
|
|
|
|
#ifndef TRAIL_MATE_ENABLE_BLE
|
|
#define TRAIL_MATE_ENABLE_BLE 0
|
|
#endif
|
|
|
|
#if TRAIL_MATE_ENABLE_BLE && __has_include("ble/ble_manager.h")
|
|
#include "ble/ble_manager.h"
|
|
#define TRAIL_MATE_UI_STATUS_HAS_BLE_MANAGER_HEADER 1
|
|
#else
|
|
#define TRAIL_MATE_UI_STATUS_HAS_BLE_MANAGER_HEADER 0
|
|
#endif
|
|
|
|
extern "C"
|
|
{
|
|
extern const lv_image_dsc_t gps_topbar;
|
|
extern const lv_image_dsc_t message_topbar;
|
|
extern const lv_image_dsc_t route_topbar;
|
|
extern const lv_image_dsc_t wifi_topbar;
|
|
#if !defined(GAT562_NO_TEAM) || !GAT562_NO_TEAM
|
|
extern const lv_image_dsc_t team_topbar;
|
|
#endif
|
|
extern const lv_image_dsc_t tracker_topbar;
|
|
#if TRAIL_MATE_ENABLE_BLE
|
|
extern const lv_image_dsc_t ble_topbar;
|
|
#endif
|
|
extern const lv_image_dsc_t lora_mod_topbar;
|
|
extern const lv_image_dsc_t fsk_mod_topbar;
|
|
extern const lv_image_dsc_t walkie_monitor_topbar;
|
|
}
|
|
|
|
namespace ui
|
|
{
|
|
namespace status
|
|
{
|
|
|
|
namespace
|
|
{
|
|
struct StatusSnapshot
|
|
{
|
|
bool route_active = false;
|
|
bool track_recording = false;
|
|
bool gps_enabled = false;
|
|
bool wifi_enabled = false;
|
|
bool team_active = false;
|
|
#if TRAIL_MATE_ENABLE_BLE
|
|
bool ble_enabled = false;
|
|
#endif
|
|
bool radio_mod_visible = false;
|
|
bool radio_mod_fsk = false;
|
|
bool walkie_monitor = false;
|
|
int unread = 0;
|
|
};
|
|
|
|
struct TeamSnapshotCache
|
|
{
|
|
bool valid = false;
|
|
uint32_t last_refresh_ms = 0;
|
|
bool team_active = false;
|
|
int team_unread = 0;
|
|
};
|
|
|
|
lv_timer_t* s_status_timer = nullptr;
|
|
lv_obj_t* s_menu_status_row = nullptr;
|
|
lv_obj_t* s_menu_route_icon = nullptr;
|
|
lv_obj_t* s_menu_tracker_icon = nullptr;
|
|
lv_obj_t* s_menu_gps_icon = nullptr;
|
|
lv_obj_t* s_menu_wifi_icon = nullptr;
|
|
lv_obj_t* s_menu_team_icon = nullptr;
|
|
lv_obj_t* s_menu_msg_icon = nullptr;
|
|
lv_obj_t* s_menu_ble_icon = nullptr;
|
|
lv_obj_t* s_menu_radio_mod_icon = nullptr;
|
|
lv_obj_t* s_menu_walkie_monitor_icon = nullptr;
|
|
lv_obj_t* s_chat_badge = nullptr;
|
|
lv_obj_t* s_chat_badge_label = nullptr;
|
|
TeamSnapshotCache s_team_cache;
|
|
constexpr uint32_t kTeamSnapshotRefreshMs = 5000;
|
|
bool s_menu_active = true;
|
|
|
|
::ui::gps::GpsStatusModel& gpsStatusModel()
|
|
{
|
|
static ::ui::gps::GpsStatusModel model(
|
|
::ui::presentation_sources::runtime_gps_status_source());
|
|
return model;
|
|
}
|
|
|
|
bool obj_valid(lv_obj_t* obj)
|
|
{
|
|
return obj && lv_obj_is_valid(obj);
|
|
}
|
|
|
|
void refresh_team_cache(bool force = false)
|
|
{
|
|
#if defined(GAT562_NO_TEAM) && GAT562_NO_TEAM
|
|
(void)force;
|
|
s_team_cache.team_active = false;
|
|
s_team_cache.team_unread = 0;
|
|
s_team_cache.valid = true;
|
|
s_team_cache.last_refresh_ms = sys::millis_now();
|
|
return;
|
|
#else
|
|
const uint32_t now = sys::millis_now();
|
|
if (!force && s_team_cache.valid && (now - s_team_cache.last_refresh_ms) < kTeamSnapshotRefreshMs)
|
|
{
|
|
return;
|
|
}
|
|
|
|
team::ui::TeamUiSnapshot snap;
|
|
if (team::ui::team_ui_snapshot_store().load(snap))
|
|
{
|
|
s_team_cache.team_active = snap.in_team;
|
|
s_team_cache.team_unread = static_cast<int>(snap.team_chat_unread);
|
|
}
|
|
else
|
|
{
|
|
s_team_cache.team_active = false;
|
|
s_team_cache.team_unread = 0;
|
|
}
|
|
s_team_cache.valid = true;
|
|
s_team_cache.last_refresh_ms = now;
|
|
#endif
|
|
}
|
|
|
|
StatusSnapshot collect_status()
|
|
{
|
|
StatusSnapshot snap{};
|
|
|
|
if (app::hasAppFacade())
|
|
{
|
|
const auto& cfg = app::configFacade().getConfig();
|
|
snap.route_active = cfg.route_enabled && (cfg.route_path[0] != '\0');
|
|
#if TRAIL_MATE_ENABLE_BLE && !defined(TRAIL_MATE_FINAL_IDF_NO_APP_FACADE)
|
|
snap.ble_enabled = app::runtimeFacade().isBleEnabled();
|
|
#if TRAIL_MATE_UI_STATUS_HAS_BLE_MANAGER_HEADER
|
|
if (auto* ble = app::runtimeFacade().getBleManager())
|
|
{
|
|
snap.ble_enabled = ble->isEnabled();
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
snap.track_recording = platform::ui::tracker::is_recording();
|
|
const auto gps = gpsStatusModel().snapshot();
|
|
snap.gps_enabled = gps.header.valid && gps.receiver_enabled;
|
|
snap.wifi_enabled = platform::ui::wifi::status().enabled;
|
|
const auto walkie = platform::ui::walkie::get_status();
|
|
snap.radio_mod_visible = platform::ui::lora::is_supported() ||
|
|
platform::ui::walkie::is_supported() ||
|
|
walkie.active || walkie.monitor_enabled;
|
|
snap.radio_mod_fsk = walkie.active;
|
|
snap.walkie_monitor = walkie.monitor_enabled;
|
|
|
|
refresh_team_cache();
|
|
snap.team_active = s_team_cache.team_active;
|
|
|
|
snap.unread = get_total_unread();
|
|
return snap;
|
|
}
|
|
|
|
void apply_icon(lv_obj_t* icon, const lv_image_dsc_t* src, bool visible)
|
|
{
|
|
if (!obj_valid(icon))
|
|
{
|
|
return;
|
|
}
|
|
if (src)
|
|
{
|
|
lv_image_set_src(icon, src);
|
|
}
|
|
if (visible)
|
|
{
|
|
lv_obj_clear_flag(icon, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
else
|
|
{
|
|
lv_obj_add_flag(icon, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
}
|
|
|
|
void apply_menu_icons(const StatusSnapshot& snap)
|
|
{
|
|
if (!obj_valid(s_menu_status_row))
|
|
{
|
|
return;
|
|
}
|
|
|
|
apply_icon(s_menu_route_icon, &route_topbar, snap.route_active);
|
|
apply_icon(s_menu_tracker_icon, &tracker_topbar, snap.track_recording);
|
|
apply_icon(s_menu_gps_icon, &gps_topbar, snap.gps_enabled);
|
|
apply_icon(s_menu_wifi_icon, &wifi_topbar, snap.wifi_enabled);
|
|
#if !defined(GAT562_NO_TEAM) || !GAT562_NO_TEAM
|
|
apply_icon(s_menu_team_icon, &team_topbar, snap.team_active);
|
|
#else
|
|
apply_icon(s_menu_team_icon, nullptr, false);
|
|
#endif
|
|
apply_icon(s_menu_msg_icon, &message_topbar, snap.unread > 0);
|
|
#if TRAIL_MATE_ENABLE_BLE
|
|
apply_icon(s_menu_ble_icon, &ble_topbar, snap.ble_enabled);
|
|
#else
|
|
apply_icon(s_menu_ble_icon, nullptr, false);
|
|
#endif
|
|
apply_icon(s_menu_radio_mod_icon,
|
|
snap.radio_mod_fsk ? &fsk_mod_topbar : &lora_mod_topbar,
|
|
snap.radio_mod_visible);
|
|
apply_icon(s_menu_walkie_monitor_icon, &walkie_monitor_topbar, snap.walkie_monitor);
|
|
|
|
const bool any = snap.route_active || snap.track_recording || snap.gps_enabled ||
|
|
snap.wifi_enabled || snap.team_active ||
|
|
#if TRAIL_MATE_ENABLE_BLE
|
|
snap.ble_enabled ||
|
|
#endif
|
|
snap.radio_mod_visible || snap.walkie_monitor || (snap.unread > 0);
|
|
if (any)
|
|
{
|
|
lv_obj_clear_flag(s_menu_status_row, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
else
|
|
{
|
|
lv_obj_add_flag(s_menu_status_row, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
}
|
|
|
|
void apply_menu_badge(const StatusSnapshot& snap)
|
|
{
|
|
if (!obj_valid(s_chat_badge) || !obj_valid(s_chat_badge_label))
|
|
{
|
|
return;
|
|
}
|
|
if (snap.unread <= 0)
|
|
{
|
|
lv_obj_add_flag(s_chat_badge, LV_OBJ_FLAG_HIDDEN);
|
|
return;
|
|
}
|
|
|
|
char buf[4];
|
|
if (snap.unread > 99)
|
|
{
|
|
snprintf(buf, sizeof(buf), "99+");
|
|
}
|
|
else
|
|
{
|
|
snprintf(buf, sizeof(buf), "%d", snap.unread);
|
|
}
|
|
lv_label_set_text(s_chat_badge_label, buf);
|
|
lv_obj_clear_flag(s_chat_badge, LV_OBJ_FLAG_HIDDEN);
|
|
}
|
|
|
|
void status_timer_cb(lv_timer_t* /*timer*/)
|
|
{
|
|
if (!s_menu_active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StatusSnapshot snap = collect_status();
|
|
|
|
apply_menu_icons(snap);
|
|
apply_menu_badge(snap);
|
|
}
|
|
} // namespace
|
|
|
|
void init()
|
|
{
|
|
if (s_status_timer)
|
|
{
|
|
return;
|
|
}
|
|
s_status_timer = lv_timer_create(status_timer_cb, 1000, nullptr);
|
|
if (s_status_timer)
|
|
{
|
|
lv_timer_set_repeat_count(s_status_timer, -1);
|
|
}
|
|
status_timer_cb(nullptr);
|
|
}
|
|
|
|
void register_menu_status_row(lv_obj_t* row,
|
|
lv_obj_t* route_icon,
|
|
lv_obj_t* tracker_icon,
|
|
lv_obj_t* gps_icon,
|
|
lv_obj_t* wifi_icon,
|
|
lv_obj_t* team_icon,
|
|
lv_obj_t* msg_icon,
|
|
lv_obj_t* ble_icon,
|
|
lv_obj_t* radio_mod_icon,
|
|
lv_obj_t* walkie_monitor_icon)
|
|
{
|
|
s_menu_status_row = row;
|
|
s_menu_route_icon = route_icon;
|
|
s_menu_tracker_icon = tracker_icon;
|
|
s_menu_gps_icon = gps_icon;
|
|
s_menu_wifi_icon = wifi_icon;
|
|
s_menu_team_icon = team_icon;
|
|
s_menu_msg_icon = msg_icon;
|
|
s_menu_ble_icon = ble_icon;
|
|
s_menu_radio_mod_icon = radio_mod_icon;
|
|
s_menu_walkie_monitor_icon = walkie_monitor_icon;
|
|
status_timer_cb(nullptr);
|
|
}
|
|
|
|
void register_chat_badge(lv_obj_t* badge_bg, lv_obj_t* badge_label)
|
|
{
|
|
s_chat_badge = badge_bg;
|
|
s_chat_badge_label = badge_label;
|
|
status_timer_cb(nullptr);
|
|
}
|
|
|
|
void force_update()
|
|
{
|
|
refresh_team_cache(true);
|
|
status_timer_cb(nullptr);
|
|
}
|
|
|
|
void set_menu_active(bool active)
|
|
{
|
|
s_menu_active = active;
|
|
if (s_status_timer == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (active)
|
|
{
|
|
lv_timer_resume(s_status_timer);
|
|
status_timer_cb(nullptr);
|
|
}
|
|
else
|
|
{
|
|
lv_timer_pause(s_status_timer);
|
|
}
|
|
}
|
|
|
|
int get_total_unread()
|
|
{
|
|
int unread = 0;
|
|
#if !defined(TRAIL_MATE_FINAL_IDF_NO_APP_FACADE)
|
|
if (app::hasAppFacade())
|
|
{
|
|
chat::ChatService& chat = app::messagingFacade().getChatService();
|
|
unread = chat.getTotalUnread();
|
|
}
|
|
#endif
|
|
refresh_team_cache();
|
|
return unread + s_team_cache.team_unread;
|
|
}
|
|
|
|
} // namespace status
|
|
} // namespace ui
|