diff --git a/docs/development/l2-hardware-checklist.md b/docs/development/l2-hardware-checklist.md index 3a050a05..3685fdbf 100644 --- a/docs/development/l2-hardware-checklist.md +++ b/docs/development/l2-hardware-checklist.md @@ -290,3 +290,5 @@ The speaker plays through the ES8311 codec (I2S MCLK 10 / BCK 11 / WS 12 / DOUT - [ ] Own channel post: "1s ✓ 2" (green, number of repeaters heard relaying it) under the bubble, nothing before an echo; DM: ✓ delivered, ✗ (red) not delivered, "..." sending - [ ] Hold a message: quote, time + hops, path diagram sender → repeaters → this device; own post: "Relayed by n repeaters" + names; own DM: Delivered / Not delivered / Sending in words - [ ] Map download popup: no WiFi button, fits the screen (scrolls if the unfinished-job row is shown); no network saved → toast pointing to Settings > WiFi +- [ ] Unlock (slider or right PIN): the lock fades away while the screen underneath drifts up into place, no instant jump +- [ ] Lock slider: knob snaps to the end near it but only unlocks on letting go; let go earlier → it springs back smoothly diff --git a/examples/companion_radio/ui-lvgl/Anim.h b/examples/companion_radio/ui-lvgl/Anim.h index dc436d2a..deeb607d 100644 --- a/examples/companion_radio/ui-lvgl/Anim.h +++ b/examples/companion_radio/ui-lvgl/Anim.h @@ -9,11 +9,14 @@ namespace anim { static const uint32_t POP_MS = 160; // popup / toast in static const uint32_t OUT_MS = 120; // toast out static const uint32_t PAGE_MS = 160; // Home page after a swipe + static const uint32_t UNLOCK_MS = 260; // lock screen away + static const uint32_t SPRING_MS = 200; // a let-go slider knob back home static void setTy(void* o, int32_t v) { lv_obj_set_style_translate_y((lv_obj_t*)o, v, 0); } static void setTx(void* o, int32_t v) { lv_obj_set_style_translate_x((lv_obj_t*)o, v, 0); } static void setOpa(void* o, int32_t v) { lv_obj_set_style_opa((lv_obj_t*)o, (lv_opa_t)v, 0); } static void setBgOpa(void* o, int32_t v) { lv_obj_set_style_bg_opa((lv_obj_t*)o, (lv_opa_t)v, 0); } + static void setSlider(void* o, int32_t v) { lv_slider_set_value((lv_obj_t*)o, v, LV_ANIM_OFF); } static void run(lv_obj_t* o, lv_anim_exec_xcb_t cb, int32_t from, int32_t to, uint32_t ms, lv_anim_completed_cb_t done = nullptr) { diff --git a/examples/companion_radio/ui-lvgl/ChannelScreen.h b/examples/companion_radio/ui-lvgl/ChannelScreen.h index c1d1d828..99752c17 100644 --- a/examples/companion_radio/ui-lvgl/ChannelScreen.h +++ b/examples/companion_radio/ui-lvgl/ChannelScreen.h @@ -76,7 +76,7 @@ void UITask::channelMenu(int idx) { s_del_armed_ms = 0; s_del_lbl = s_fav_btn = nullptr; char title[40]; - snprintf(title, sizeof(title), "%s%s", ch.name[0] == '#' ? "" : "# ", ch.name); // "#topic" has its own + snprintf(title, sizeof(title), "%s", ch.name); // as named: "#" marks a hashtag channel lv_obj_t* panel = navPopupPanel(title, false); // Notifications: three segments in the row (a dropdown's list ran off the diff --git a/examples/companion_radio/ui-lvgl/DeviceScreen.h b/examples/companion_radio/ui-lvgl/DeviceScreen.h index 3ec42849..3d20cfa6 100644 --- a/examples/companion_radio/ui-lvgl/DeviceScreen.h +++ b/examples/companion_radio/ui-lvgl/DeviceScreen.h @@ -237,8 +237,19 @@ void UITask::unlockScreen() { using namespace devview; if (!s_lock) return; lv_indev_wait_release(lv_indev_active()); // the slide isn't also a tap underneath - lv_obj_delete_async(s_lock); - s_lock = s_lock_clock = s_lock_date = s_lock_unread = s_lock_slider = s_pin_dots = s_pin_msg = nullptr; + // The lock's content goes at once and its background fades away as the + // screen underneath drifts up into place -- the same cheap cover fade as a + // screen change (Anim.h), which deletes the overlay at the end. + lv_obj_t* fading = s_lock; + for (uint32_t i = 0; i < lv_obj_get_child_count(fading); i++) + lv_obj_add_flag(lv_obj_get_child(fading, i), LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(fading, LV_OBJ_FLAG_CLICKABLE); + if (_asleep) lv_obj_delete_async(fading); + else { + anim::run(fading, anim::setBgOpa, LV_OPA_COVER, LV_OPA_TRANSP, anim::UNLOCK_MS, anim::coverDone); + if (_body) anim::run(_body, anim::setTy, 12, 0, anim::UNLOCK_MS); + } + s_lock =s_lock_clock = s_lock_date = s_lock_unread = s_lock_slider = s_pin_dots = s_pin_msg = nullptr; _pin_entry[0] = '\0'; if (_screen == SCR_HOME) refreshHome(); else if (_screen == SCR_CHATS) buildChats(); // counts moved on while locked @@ -246,9 +257,10 @@ void UITask::unlockScreen() { bool UITask::locked() const { return devview::s_lock != nullptr; } -// From loop() while locked: the knob follows the finger. Tracked from the -// touch itself, like the Home page swipe -- LVGL stops feeding a pressed -// widget once the finger has moved past its scroll threshold. +// From loop() while locked: the knob follows the finger and unlocks when let +// go at the end (a knob let go earlier springs back). Tracked from the touch +// itself, like the Home page swipe -- LVGL stops feeding a pressed widget +// once the finger has moved past its scroll threshold. void UITask::lockPoll() { using namespace devview; static bool s_dragging = false, s_was_down = false; @@ -262,19 +274,22 @@ void UITask::lockPoll() { int32_t knob = lv_area_get_height(&a); // round knob, as tall as the track if (down && !s_was_down) { // a drag starts on the knob only s_dragging = p.y >= a.y1 - 10 && p.y <= a.y2 + 10 && p.x <= a.x1 + knob + 16; + if (s_dragging) lv_anim_delete(s_lock_slider, anim::setSlider); } s_was_down = down; if (!down) { - if (s_dragging || lv_slider_get_value(s_lock_slider) != 0) lv_slider_set_value(s_lock_slider, 0, LV_ANIM_OFF); + if (!s_dragging) return; s_dragging = false; + int32_t v = lv_slider_get_value(s_lock_slider); + if (v >= 100) unlockScreen(); + else if (v > 0) anim::run(s_lock_slider, anim::setSlider, v, 0, anim::SPRING_MS); return; } if (!s_dragging) return; int32_t span = lv_area_get_width(&a) - knob; int32_t v = span > 0 ? (p.x - a.x1 - knob / 2) * 100 / span : 0; - v = v < 0 ? 0 : v > 100 ? 100 : v; + v = v < 0 ? 0 : v >= 92 ? 100 : v; // snaps to the end: let go to unlock lv_slider_set_value(s_lock_slider, v, LV_ANIM_OFF); - if (v >= 95) { s_dragging = s_was_down = false; unlockScreen(); } } void UITask::refreshLock() { diff --git a/examples/companion_radio/ui-lvgl/NavMap.h b/examples/companion_radio/ui-lvgl/NavMap.h index b16e5757..c5673e67 100644 --- a/examples/companion_radio/ui-lvgl/NavMap.h +++ b/examples/companion_radio/ui-lvgl/NavMap.h @@ -836,8 +836,9 @@ void UITask::navToolsPopup() { _nav_share_lbl = label(list, "", THEME_FONT_SMALL, theme::TEXT); lv_label_set_long_mode(_nav_share_lbl, LV_LABEL_LONG_WRAP); lv_obj_set_width(_nav_share_lbl, LV_PCT(100)); - // Target: channels, then favourite contacts. - char opts[MAX_GROUP_CHANNELS * 24 + 16 * 36]; + // Target: channels (as named -- "#" marks a hashtag channel), then + // favourite contacts (starred, so the two stay apart). + char opts[MAX_GROUP_CHANNELS * 24 + 16 * 40]; int o = 0, sel = 0; navmap::s_share_n = 0; for (int i = 0; i < MAX_GROUP_CHANNELS && navmap::s_share_n < navmap::SHARE_TARGETS; i++) { @@ -846,7 +847,7 @@ void UITask::navToolsPopup() { if (_prefs->loc_share_target_type == 0 && _prefs->loc_share_channel_idx == i) sel = navmap::s_share_n; navmap::s_share_kind[navmap::s_share_n] = 0; navmap::s_share_ch[navmap::s_share_n++] = (uint8_t)i; - o += snprintf(opts + o, sizeof(opts) - o, "%s# %s", o ? "\n" : "", ch.name); + o += snprintf(opts + o, sizeof(opts) - o, "%s%s", o ? "\n" : "", ch.name); } int favs = 0; for (int i = 0; i < the_mesh.getNumContacts() && favs < 16 && navmap::s_share_n < navmap::SHARE_TARGETS; i++) { @@ -857,7 +858,7 @@ void UITask::navToolsPopup() { memcmp(_prefs->loc_share_dm_prefix, c.id.pub_key, NodePrefs::FAVOURITE_PREFIX_LEN) == 0) sel = navmap::s_share_n; navmap::s_share_kind[navmap::s_share_n] = 1; memcpy(navmap::s_share_key[navmap::s_share_n++], c.id.pub_key, NodePrefs::FAVOURITE_PREFIX_LEN); - o += snprintf(opts + o, sizeof(opts) - o, "%s%s", o ? "\n" : "", c.name); + o += snprintf(opts + o, sizeof(opts) - o, "%s" UI_SYMBOL_STAR " %s", o ? "\n" : "", c.name); favs++; } lv_obj_t* tr = lv_obj_create(list); diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 46ec9c95..017d19e2 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -1788,7 +1788,7 @@ void UITask::buildThread() { bool can_send = true; if (_thread_is_channel) { ChannelDetails ch; - if (the_mesh.getChannel(_thread_channel, ch)) snprintf(title, sizeof(title), "%s%s", ch.name[0] == '#' ? "" : "# ", ch.name); + if (the_mesh.getChannel(_thread_channel, ch)) snprintf(title, sizeof(title), "%s", ch.name); // as named: "#" marks a hashtag channel else snprintf(title, sizeof(title), "Channel %d", _thread_channel); } else { contactName(_thread_key, title, sizeof(title));