diff --git a/examples/companion_radio/ui-lvgl/DeviceScreen.h b/examples/companion_radio/ui-lvgl/DeviceScreen.h index 2f9d5b91..7fe5cfc7 100644 --- a/examples/companion_radio/ui-lvgl/DeviceScreen.h +++ b/examples/companion_radio/ui-lvgl/DeviceScreen.h @@ -224,6 +224,7 @@ void UITask::unlockScreen() { _pin_entry[0] = '\0'; if (_screen == SCR_HOME) refreshHome(); else if (_screen == SCR_CHATS) refreshChats(); // counts moved on while locked + else if (_screen == SCR_THREAD) reopenThreadIfUnread(); // on to what came in meanwhile } bool UITask::locked() const { return devview::s_lock != nullptr; } diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 89212a34..4baa0194 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -1735,7 +1735,7 @@ void UITask::wake() { if (_display) _display->turnOn(); // dark until the frame below is on the panel refreshStatusBar(); if (_screen == SCR_HOME) refreshHome(); - if (_screen == SCR_THREAD) refreshThread(); + if (_screen == SCR_THREAD) { if (locked()) refreshThread(); else reopenThreadIfUnread(); } // locked: on unlocking lv_obj_invalidate(lv_screen_active()); lv_refr_now(NULL); lvport::backlightFadeIn(); @@ -1913,12 +1913,13 @@ void UITask::onMessageArrived(const UiEvent& ev) { if (_screen == SCR_CHATS && !_nav_overlay) refreshChats(); // new unread counts (not under an open popup) } +// On screen: a conversation left open under the lock or a dark display isn't read. bool UITask::isViewingChannel(uint8_t channel_idx) { - return _screen == SCR_THREAD && _thread_is_channel && _thread_channel == channel_idx; + return !_asleep && !locked() && _screen == SCR_THREAD && _thread_is_channel && _thread_channel == channel_idx; } bool UITask::isViewingDM(const uint8_t* pub_key) { - return _screen == SCR_THREAD && !_thread_is_channel && memcmp(_thread_key, pub_key, 4) == 0; + return !_asleep && !locked() && _screen == SCR_THREAD && !_thread_is_channel && memcmp(_thread_key, pub_key, 4) == 0; } // ── Status bar + toast (top layer, over every screen) ───────────────────────── @@ -3631,6 +3632,21 @@ void UITask::threadOpenAt(int first) { _thread_skip = first < THREAD_MAX_SHOWN ? 0 : first - (THREAD_MAX_SHOWN - 4); } +// Back on over a conversation: what came in while asleep opens it again on +// its first unread (the typed text kept, the keyboard down); else just the +// fresh content. +void UITask::reopenThreadIfUnread() { + bool unread = _thread_is_channel ? _core->history.chUnread(_thread_channel) > 0 : _core->dmUnread(_thread_key) > 0; + if (!unread || _nav_overlay) { refreshThread(); return; } + char typed[MSG_TEXT_BUF] = ""; + if (_compose_ta) snprintf(typed, sizeof(typed), "%s", lv_textarea_get_text(_compose_ta)); + uint8_t key[PUB_KEY_SIZE]; // openDM() clears _thread_key before reading its argument + memcpy(key, _thread_key, sizeof(key)); + if (_thread_is_channel) openChannel(_thread_channel); + else openDM(key); + if (_compose_ta && typed[0]) { lv_textarea_set_text(_compose_ta, typed); composeLeftRefresh(_compose_ta); } +} + void UITask::openChannel(uint8_t channel_idx) { _thread_is_channel = true; _thread_channel = channel_idx; @@ -4747,6 +4763,10 @@ extern "C" EMSCRIPTEN_KEEPALIVE void sim_open(const char* name) { if (!strcmp(name, "dm")) { s_ui->simDm(); return; } if (!strcmp(name, "send")) { s_ui->simSend(); return; } if (!strcmp(name, "unread")) { s_ui->simUnread(); return; } + if (!strcmp(name, "sleep")) { s_ui->simSleep(true); return; } + if (!strcmp(name, "wake")) { s_ui->simSleep(false); return; } + if (!strcmp(name, "lock")) { s_ui->simLock(true); return; } + if (!strcmp(name, "unlock")) { s_ui->simLock(false); return; } if (!strcmp(name, "vector")) { s_ui->setVectorMap(true); return; } if (!strcmp(name, "maptools")) { s_ui->navToolsPopup(); return; } if (!strcmp(name, "areasel")) { s_ui->areaSelectBegin(); return; } diff --git a/examples/companion_radio/ui-lvgl/UITask.h b/examples/companion_radio/ui-lvgl/UITask.h index 178163cb..effdc99e 100644 --- a/examples/companion_radio/ui-lvgl/UITask.h +++ b/examples/companion_radio/ui-lvgl/UITask.h @@ -131,6 +131,8 @@ public: void simCompose(const char* text); // the open thread's field: this text, keyboard up void simDm(); // the first chat contact's thread void simUnread(); // read and new messages to open on + void simSleep(bool on) { if (on) sleep(); else wake(); } + void simLock(bool on) { if (on) lockScreen(); else unlockScreen(); } void simSend() { sendFromCompose(); } // the field, sent // Navigation map (NavMap.h) void navTargetsPopup(); @@ -427,6 +429,7 @@ private: void refreshNode(); void buildThread(); void threadOpenAt(int first); + void reopenThreadIfUnread(); void refreshThread(); void refreshThreadAges(); uint32_t threadSignature() const; diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index 46b15666..a0a8ab5c 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -1063,6 +1063,13 @@ public: _dm_hist_scroll = 0; _dm_fs.active = false; } + // The display came back on over a conversation: what was filed while it was + // dark is unread -- read on from its first message, as on opening. + void onDisplayOn() { + if (_ctx_menu.active) return; + if (_phase == CHANNEL_HIST && !_fs.active && _history.chUnread(_sel_channel_idx) > 0) beginChannelView(_sel_channel_idx); + else if (_phase == DM_HIST && !_dm_fs.active && _task->getDMUnread(_sel_contact.id.pub_key) > 0) beginDmView(); + } void beginChannelView(uint8_t channel_idx) { _sel_channel_idx = channel_idx; _unread_at_entry = (int)_history.chUnread(channel_idx); diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 83fa018e..412f2ca6 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2221,6 +2221,12 @@ void UITask::simUnread(int which) { } } +void UITask::simDisplay(bool on) { + if (!_display) return; + if (on) _display->turnOn(); else _display->turnOff(); + _next_refresh = 0; +} + void UITask::simCompose(const char* text) { gotoMessagesScreen(); ((MessagesScreen*)messages_screen)->simCompose(text); @@ -2273,11 +2279,14 @@ void UITask::pickBotRoomTarget() { // ── UI Core wiring ────────────────────────────────────────────────────────── MyMesh::Listener* UITask::meshListener() { return _core; } +// On screen: a conversation left open under the lock or a dark display isn't read. bool UITask::isViewingChannel(uint8_t channel_idx) { - return ((MessagesScreen*)messages_screen)->isViewingChannel(channel_idx); + return curr == messages_screen && _display && _display->isOn() && !_locked + && ((MessagesScreen*)messages_screen)->isViewingChannel(channel_idx); } bool UITask::isViewingDM(const uint8_t* pub_key) { - return ((MessagesScreen*)messages_screen)->isViewingDM(pub_key); + return curr == messages_screen && _display && _display->isOn() && !_locked + && ((MessagesScreen*)messages_screen)->isViewingDM(pub_key); } void UITask::onViewedHistoryGrew(bool channel) { ((MessagesScreen*)messages_screen)->onViewedHistoryGrew(channel); @@ -2732,6 +2741,11 @@ extern "C" EMSCRIPTEN_KEEPALIVE void sim_unread(int which) { if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->simUnread(which); } +// Sim tests: the display off (0) or on (1), as the auto-off and a key press do. +extern "C" EMSCRIPTEN_KEEPALIVE void sim_display(int on) { + if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->simDisplay(on != 0); +} + // Sim tests: a channel message arrives, alerted in Settings > Message alert's // style (0 Normal, 1 Compact, 2 Off). extern "C" EMSCRIPTEN_KEEPALIVE void sim_message(const char* text, int style) { @@ -3155,6 +3169,11 @@ void UITask::loop() { #endif pollConnection(); // BLE link state -> hasConnection() (see UITaskBase) drainCoreEvents(); // react to what the Core filed during mesh processing (alerts, wake, sounds) + { // a conversation in view again (display on, unlocked): on to what came in meanwhile + bool on = _display && _display->isOn() && !_locked; + if (on && !_display_was_on && curr == messages_screen) ((MessagesScreen*)messages_screen)->onDisplayOn(); + _display_was_on = on; + } if (status_screen) ((StatusScreen*)status_screen)->sample(); // Status history lines, once a minute #if UI_HAS_JOYSTICK uint8_t joy_rot = _node_prefs ? _node_prefs->joystick_rotation : JOYSTICK_ROTATION; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index d60a4d43..04a61bff 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -60,6 +60,7 @@ class UITask : public UITaskBase, public UiCoreHost { GenericVibration vibration; #endif unsigned long _next_refresh, _auto_off; + bool _display_was_on = true; // loop(): the display back on and unlocked NodePrefs* _node_prefs; bool _locked; bool _tool_from_home = false; @@ -285,6 +286,7 @@ public: void simCompose(const char* text); // sim tests: a channel post's keyboard holding `text` void simMessage(const char* text); // sim tests: a channel message arrives void simUnread(int which); // sim tests: read and new messages, opened + void simDisplay(bool on); // sim tests: the display off / on #endif void openContactDM(const ContactInfo& ci); void openChannelHistory(uint8_t channel_idx); // Favourites dial: open a pinned channel