mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-11-11 14:47:39 +00:00
fix(ui): qualify Cardputer Zero media and tracker rehearsal flows
This commit is contained in:
1 parent
a2125d7da3
commit
e2eb24074a
8 files changed
+128
-22
No files matched your search
@@ -22,6 +22,8 @@ target-specific; see the linked acceptance records below.
|
||||
- Cardputer Zero ARM64 Debian package with APPLaunch integration, framebuffer
|
||||
rendering, physical keyboard navigation, hardware permissions, Cap LoRa/GNSS
|
||||
detection, heading integration and the shared launcher/widget screens.
|
||||
- Cardputer Zero access to the shared music player, including its compact
|
||||
now-playing layout and launcher-widget controls.
|
||||
- T-Display-CM0 Raspbian ARMHF application and matching native 1seg backend
|
||||
packages, preserving the target's GCC 14 C++ ABI and ARMv7 receive contract.
|
||||
CM0 also gains the LR2021 FM/1seg views, OFDM learning workbench and receiver
|
||||
@@ -37,6 +39,10 @@ target-specific; see the linked acceptance records below.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Tracker file cards refresh automatically when storage availability changes,
|
||||
including completion of the initial asynchronous Linux storage probe.
|
||||
- Shared route-photo preview state preserves verified fullscreen-cache readiness
|
||||
instead of falling back to original JPEG files and showing corrupted images.
|
||||
- Linux application launchers display the product name `TrailMate`, including
|
||||
the Cardputer Zero window title and notification source.
|
||||
- TMAP area searches defer page reconstruction until textarea input events have
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
#include "../../../modules/ui_shared/src/ui/screens/music/music_state.h"
|
||||
#include "app/input_event.h"
|
||||
#include "platform/ui/orientation_runtime.h"
|
||||
#include "ui/app_runtime.h"
|
||||
#include "ui/menu/dashboard/dashboard_state.h"
|
||||
#include "ui/menu/menu_dashboard.h"
|
||||
#include "ui/menu/menu_layout.h"
|
||||
#include "ui/screens/tracker/tracker_state.h"
|
||||
#include "ui/shell_ui_runner.h"
|
||||
#include "ui/widgets/route_image_strip.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -84,6 +90,80 @@ int main()
|
||||
assert(ui::menu::dashboard::isFeedOpen());
|
||||
key(InputKey::Home);
|
||||
assert(!ui::menu::dashboard::isFeedOpen());
|
||||
assert(ui::menu_layout::launchAppByStableId("music"));
|
||||
tick(100ms);
|
||||
auto* music = ui::music_page::state();
|
||||
assert(music && music->service && music->root);
|
||||
ui::music_page::navigate(ui::music_page::View::Playing);
|
||||
lv_obj_update_layout(music->root);
|
||||
lv_area_t album, progress, elapsed;
|
||||
lv_obj_get_coords(music->album, &album);
|
||||
lv_obj_get_coords(music->progress, &progress);
|
||||
lv_obj_get_coords(music->elapsed, &elapsed);
|
||||
assert(album.y2 + 3 < progress.y1);
|
||||
assert(progress.y2 < elapsed.y1 && elapsed.y2 < 146);
|
||||
key(InputKey::Home);
|
||||
const auto storage = root / "sd";
|
||||
const auto retained = root / "sd-retained";
|
||||
std::filesystem::create_directories(storage / "tracks");
|
||||
std::ofstream(storage / "tracks/storage-ready.gpx")
|
||||
<< "<gpx><trk><name>Storage ready</name><trkseg><trkpt lat=\"51.5\" lon=\"-0.17\"/></trkseg></trk></gpx>";
|
||||
assert(ui::menu_layout::launchAppByStableId("tracker"));
|
||||
tick(1600ms);
|
||||
auto& tracker = ::tracker::ui::g_tracker_state;
|
||||
assert(tracker.card_counter && !std::strcmp(lv_label_get_text(tracker.card_counter), "1 / 1"));
|
||||
// Exercise the real asynchronous storage probe: recover the open page
|
||||
// after its root becomes unavailable, without leaving/reentering it.
|
||||
std::filesystem::rename(storage, retained);
|
||||
std::ofstream(storage) << "temporarily unavailable volume";
|
||||
tick(1600ms);
|
||||
assert(!std::strcmp(lv_label_get_text(tracker.card_name), "No SD Card"));
|
||||
std::filesystem::remove(storage);
|
||||
std::filesystem::rename(retained, storage);
|
||||
tick(1600ms);
|
||||
assert(!std::strcmp(lv_label_get_text(tracker.card_counter), "1 / 1"));
|
||||
key(InputKey::Home);
|
||||
std::vector<uint8_t> bitmap(54 + 320 * 180 * 3, 0);
|
||||
const auto integer = [&](size_t offset, uint32_t value)
|
||||
{ for (unsigned byte=0;byte<4;++byte) bitmap[offset+byte]=uint8_t(value>>(byte*8)); };
|
||||
bitmap[0] = 'B';
|
||||
bitmap[1] = 'M';
|
||||
bitmap[26] = 1;
|
||||
bitmap[28] = 24;
|
||||
integer(2, bitmap.size());
|
||||
integer(10, 54);
|
||||
integer(14, 40);
|
||||
integer(18, 320);
|
||||
integer(22, 180);
|
||||
for (unsigned y = 0; y < 180; ++y)
|
||||
for (unsigned x = 0; x < 320; ++x) bitmap[54 + ((179 - y) * 320 + x) * 3 + (y < 60 ? 2 : y < 120 ? 1
|
||||
: 0)] = 255;
|
||||
const auto image_path = (root / "fullscreen.bmp").string();
|
||||
std::ofstream(image_path, std::ios::binary).write(reinterpret_cast<const char*>(bitmap.data()), bitmap.size());
|
||||
ui::widgets::route_image_strip::Widget photos;
|
||||
ui::widgets::route_image_strip::create(lv_screen_active(), photos);
|
||||
ui::widgets::route_image_strip::Item photo;
|
||||
photo.downloaded = true;
|
||||
photo.local_path = image_path;
|
||||
photo.preview_path = image_path;
|
||||
photo.view_path = image_path;
|
||||
ui::widgets::route_image_strip::set_items(photos, &photo, 1);
|
||||
ui::widgets::route_image_strip::set_hidden(photos, false);
|
||||
assert(ui::widgets::route_image_strip::handle_key(photos, LV_KEY_ENTER));
|
||||
tick(400ms);
|
||||
lv_obj_invalidate(photos.fullscreen_root);
|
||||
tick(400ms);
|
||||
for (unsigned y : {30U, 85U, 150U})
|
||||
{
|
||||
for (unsigned x : {80U, 240U})
|
||||
{
|
||||
const auto& pixel = host.canvas().pixel(x, y);
|
||||
if (y == 30) assert(pixel.r > 240 && pixel.g < 8 && pixel.b < 8);
|
||||
else if (y == 85) assert(pixel.r < 8 && pixel.g > 240 && pixel.b < 8);
|
||||
else assert(pixel.r < 8 && pixel.g < 8 && pixel.b > 240);
|
||||
}
|
||||
}
|
||||
ui::widgets::route_image_strip::destroy(photos);
|
||||
}
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
@@ -948,15 +948,14 @@ function(trailmate_add_linux_ui_shell target_name common_target)
|
||||
"${TRAIL_MATE_LINUX_COMMON_SRC_ROOT}/platform/linux/cardputer_cap.cpp")
|
||||
elseif(UI_UX_PACK STREQUAL "t_display_p4_touch")
|
||||
target_compile_definitions(${target_name} PRIVATE TRAIL_MATE_UI_P4_TOUCH=1)
|
||||
target_sources(${target_name} PRIVATE
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_page_layout.cpp"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_page_input.cpp"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_state.cpp"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_page_shell.cpp")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported Linux LVGL UX pack: ${UI_UX_PACK}")
|
||||
endif()
|
||||
target_sources(${target_name} PRIVATE
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_page_layout.cpp"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_page_input.cpp"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_state.cpp"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/music/music_page_shell.cpp"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/assets/calc.c"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/assets/geocaching.c"
|
||||
"${TRAIL_MATE_UI_SHARED_SRC_ROOT}/ui/screens/calculator/calculator_engine.cpp"
|
||||
|
||||
@@ -41,6 +41,7 @@ struct TrackerPageState
|
||||
lv_obj_t* card_distance = nullptr;
|
||||
::ui::support::CardLocation* card_location_model = nullptr;
|
||||
lv_timer_t* card_timer = nullptr;
|
||||
bool card_storage_ready = false;
|
||||
lv_obj_t* card_counter = nullptr;
|
||||
lv_obj_t* card_previous = nullptr;
|
||||
lv_obj_t* card_next = nullptr;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#define TRAIL_MATE_ENABLE_MUSIC_APP 1
|
||||
#elif defined(TRAIL_MATE_CARDPUTER_ZERO_LINUX)
|
||||
#define TRAIL_MATE_ENABLE_GEOCACHING_APP 1
|
||||
#define TRAIL_MATE_ENABLE_MUSIC_APP 0
|
||||
#define TRAIL_MATE_ENABLE_MUSIC_APP 1
|
||||
#else
|
||||
#define TRAIL_MATE_ENABLE_GEOCACHING_APP 0
|
||||
#define TRAIL_MATE_ENABLE_MUSIC_APP 0
|
||||
|
||||
@@ -126,16 +126,21 @@ void cover(lv_obj_t* parent, lv_coord_t x, lv_coord_t y, lv_coord_t edge)
|
||||
void now_playing(lv_coord_t width, lv_coord_t height)
|
||||
{
|
||||
auto& self = *state();
|
||||
const bool short_screen = !self.wide && height < 150;
|
||||
const lv_coord_t margin = self.wide ? std::max<lv_coord_t>(24, (width - 1040) / 2) : 8;
|
||||
const lv_coord_t gap = 20;
|
||||
const lv_coord_t w = self.wide ? (width - 2 * margin - gap) / 2 : width - 2 * margin;
|
||||
const lv_coord_t h = self.wide ? std::min<lv_coord_t>(400, height - 40) : height - 8;
|
||||
const lv_coord_t y = (height - h) / 2;
|
||||
auto* panel = card(self.body, margin, y, w, h);
|
||||
lv_coord_t title_y = 10, text_x = 14;
|
||||
lv_coord_t title_y = short_screen ? 4 : 10, text_x = 14;
|
||||
const lv_coord_t controls_y = h - (short_screen ? 28 : 44);
|
||||
const lv_coord_t progress_y = controls_y - (short_screen ? 26 : 34);
|
||||
// Include the slider knob's painted extent, not just its five-pixel track.
|
||||
const lv_coord_t edge = self.wide ? std::min<lv_coord_t>(200, h - 210) : std::min<lv_coord_t>(96, h - 96);
|
||||
cover(panel, self.wide ? (w - edge) / 2 : 12, self.wide ? 16 : 10, edge);
|
||||
const lv_coord_t edge = self.wide ? std::min<lv_coord_t>(200, h - 210)
|
||||
: short_screen ? std::max<lv_coord_t>(16, std::min<lv_coord_t>(48, progress_y - 8))
|
||||
: std::min<lv_coord_t>(96, h - 96);
|
||||
cover(panel, self.wide ? (w - edge) / 2 : 12, self.wide ? 16 : title_y, edge);
|
||||
if (self.wide)
|
||||
{
|
||||
title_y = edge + 32;
|
||||
@@ -143,21 +148,20 @@ void now_playing(lv_coord_t width, lv_coord_t height)
|
||||
else text_x = edge + 24;
|
||||
const lv_coord_t text_w = w - text_x - 14;
|
||||
self.title = content_label(panel, self.snapshot.current.title[0] ? self.snapshot.current.title : ui::i18n::tr("Choose a song"),
|
||||
text_x, title_y, text_w, &lv_font_montserrat_16);
|
||||
text_x, title_y, text_w, short_screen ? &lv_font_montserrat_14 : &lv_font_montserrat_16);
|
||||
lv_label_set_long_mode(self.title, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_height(self.title, 20);
|
||||
self.artist = content_label(panel, self.snapshot.current.artist, text_x, title_y + 26, text_w, &lv_font_montserrat_14, true);
|
||||
lv_obj_set_height(self.title, short_screen ? 18 : 20);
|
||||
self.artist = content_label(panel, self.snapshot.current.artist, text_x, title_y + (short_screen ? 18 : 26), text_w,
|
||||
short_screen ? &lv_font_montserrat_12 : &lv_font_montserrat_14, true);
|
||||
lv_label_set_long_mode(self.artist, LV_LABEL_LONG_DOT);
|
||||
self.album = content_label(panel, self.snapshot.current.album, text_x, title_y + 46, text_w - 44, &lv_font_montserrat_12, true);
|
||||
self.album = content_label(panel, self.snapshot.current.album, text_x, title_y + (short_screen ? 34 : 46), text_w - 44, &lv_font_montserrat_12, true);
|
||||
lv_label_set_long_mode(self.album, LV_LABEL_LONG_DOT);
|
||||
char volume[16];
|
||||
std::snprintf(volume, sizeof(volume), "%u%%", self.snapshot.volume);
|
||||
self.volume = label(panel, volume, w - 54, title_y + 46, 40, &lv_font_montserrat_12, true);
|
||||
self.volume = label(panel, volume, w - 54, title_y + (short_screen ? 34 : 46), 40, &lv_font_montserrat_12, true);
|
||||
self.playback_status = label(panel, "", text_x, title_y + 64, text_w, &lv_font_montserrat_12, true);
|
||||
lv_label_set_long_mode(self.playback_status, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_height(self.playback_status, lv_font_get_line_height(&lv_font_montserrat_12));
|
||||
const lv_coord_t controls_y = h - 44;
|
||||
const lv_coord_t progress_y = controls_y - 34;
|
||||
self.progress = lv_slider_create(panel);
|
||||
lv_obj_set_pos(self.progress, 14, progress_y);
|
||||
lv_obj_set_size(self.progress, w - 28, 5);
|
||||
@@ -168,14 +172,15 @@ void now_playing(lv_coord_t width, lv_coord_t height)
|
||||
lv_obj_set_style_pad_all(self.progress, 3, LV_PART_KNOB);
|
||||
lv_obj_add_event_cb(self.progress, action_event, LV_EVENT_RELEASED, reinterpret_cast<void*>(99));
|
||||
lv_obj_add_event_cb(self.progress, key_event, LV_EVENT_KEY, nullptr);
|
||||
self.elapsed = label(panel, "0:00", 14, progress_y + 12, 70, &lv_font_montserrat_12, true);
|
||||
self.duration = label(panel, "--:--", w - 84, progress_y + 12, 70, &lv_font_montserrat_12, true);
|
||||
const lv_coord_t time_y = progress_y + (short_screen ? 8 : 12);
|
||||
self.elapsed = label(panel, "0:00", 14, time_y, 70, &lv_font_montserrat_12, true);
|
||||
self.duration = label(panel, "--:--", w - 84, time_y, 70, &lv_font_montserrat_12, true);
|
||||
lv_obj_set_style_text_align(self.duration, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
if (!self.wide)
|
||||
{
|
||||
// Status shares the time row on compact screens, leaving the cover,
|
||||
// metadata and the slider's painted knob area independent.
|
||||
lv_obj_set_pos(self.playback_status, 92, progress_y + 12);
|
||||
lv_obj_set_pos(self.playback_status, 92, time_y);
|
||||
lv_obj_set_width(self.playback_status, w - 184);
|
||||
lv_obj_set_style_text_align(self.playback_status, LV_TEXT_ALIGN_CENTER, 0);
|
||||
}
|
||||
@@ -189,7 +194,7 @@ void now_playing(lv_coord_t width, lv_coord_t height)
|
||||
lv_obj_set_pos(previous, (w - 256) / 2 + 104, controls_y);
|
||||
lv_obj_set_pos(play, (w - 256) / 2 + 148, controls_y);
|
||||
lv_obj_set_pos(next, (w - 256) / 2 + 220, controls_y);
|
||||
for (auto* control : {mode, previous, play, next}) lv_obj_set_height(control, 30);
|
||||
for (auto* control : {mode, previous, play, next}) lv_obj_set_height(control, short_screen ? 24 : 30);
|
||||
lv_obj_set_style_bg_color(play, ui::theme::accent(), 0);
|
||||
lv_group_focus_obj(play);
|
||||
if (self.wide)
|
||||
|
||||
@@ -187,6 +187,7 @@ lv_coord_t modal_button_width();
|
||||
lv_coord_t action_menu_button_height();
|
||||
lv_coord_t action_menu_row_gap();
|
||||
void update_record_status();
|
||||
void refresh_record_list();
|
||||
void refresh_route_list();
|
||||
void update_start_stop_button();
|
||||
void update_record_page();
|
||||
@@ -898,7 +899,6 @@ void refresh_preview_image_storage_state(bool include_cache_state)
|
||||
{
|
||||
++saved_count;
|
||||
}
|
||||
image.view_ready = false;
|
||||
}
|
||||
s_preview_saved_image_count_hint = saved_count;
|
||||
s_preview_image_saved_state_known = true;
|
||||
@@ -3297,8 +3297,18 @@ void update_item_card()
|
||||
state.card_next = view.next;
|
||||
state.card_actions = view.actions;
|
||||
::ui::i18n::set_label_text(lv_obj_get_child(view.actions, 0), "Actions");
|
||||
state.card_storage_ready = platform::ui::device::sd_ready();
|
||||
state.card_timer = lv_timer_create([](lv_timer_t*)
|
||||
{ update_card_location(); },
|
||||
{
|
||||
auto& current = g_tracker_state;
|
||||
const bool ready = platform::ui::device::sd_ready();
|
||||
if (!is_any_modal_open() && current.card_storage_ready != ready)
|
||||
{
|
||||
current.card_storage_ready = ready;
|
||||
refresh_record_list();
|
||||
refresh_route_list();
|
||||
}
|
||||
update_card_location(); },
|
||||
500, nullptr);
|
||||
lv_obj_add_event_cb(card, on_list_item_clicked, LV_EVENT_CLICKED, nullptr);
|
||||
lv_obj_add_event_cb(
|
||||
|
||||
@@ -305,6 +305,11 @@ int main()
|
||||
assert(first.downloaded && first.preview_ready && first.view_ready);
|
||||
assert(preview::s_preview_saved_image_count_hint == 1);
|
||||
assert(preview::s_preview_images[0].downloaded && preview::s_preview_images[0].preview_ready && !preview::s_preview_images[1].downloaded);
|
||||
assert(preview::s_preview_images[0].view_ready);
|
||||
preview::refresh_preview_image_storage_state(false);
|
||||
assert(preview::s_preview_images[0].downloaded && !preview::s_preview_images[0].preview_ready && !preview::s_preview_images[0].view_ready);
|
||||
preview::refresh_preview_image_storage_state(true);
|
||||
assert(preview::s_preview_images[0].preview_ready && preview::s_preview_images[0].view_ready);
|
||||
platform::ui::route_storage::RouteImageDownloadStatus status;
|
||||
status.asset_id = "test";
|
||||
status.busy = true;
|
||||
|
||||
Reference in new issue
Block a user