Show button controls on companion home and message screens
This commit is contained in:
1 parent
ac694d536e
commit
74d87c8d13
6 files changed
+265
-24
No files matched your search
@@ -16,13 +16,30 @@ letters taking less space and `M` and `W` taking 6 pixels. Squeezed Regular 6
|
||||
is a [public-domain font by Oliver Kraus](https://github.com/olikraus/u8g2/wiki/fntgrpu8g#squeezed_r6).
|
||||
Its bitmap data is stored as constants without heap allocation.
|
||||
|
||||
On a 128 x 64 OLED, the compact channel/sender line starts at y=14 and message
|
||||
text at y=22. Five complete 6px-font rows fit. The software comparison fits
|
||||
three representative 160-character messages in those five rows. Capacity
|
||||
On a 128 x 64 OLED with the single-button message reader, the compact
|
||||
channel/sender line starts at y=8 and message text at y=16. The header uses
|
||||
the same small font. Five complete 6px-font rows fit above the navigation
|
||||
hint at y=56. The software comparison fits three representative 160-character
|
||||
messages in those five rows. Capacity
|
||||
depends on the characters; messages with many wide letters can still overflow.
|
||||
Text wraps at character boundaries, with `...` on the last line if necessary.
|
||||
Unsupported characters appear as `?`. Long channel/sender names are ellipsized
|
||||
to stay on their own line. An enabled channel footer reserves its own space.
|
||||
Readers without the button hint retain the y=14 origin and y=22 message text.
|
||||
|
||||
On V4 and other single-button builds using this reader, the home screen says
|
||||
`hold button: inbox`. Hold the user button for about 1.2 seconds to open it.
|
||||
The bottom of the reader shows `<- 2tap 1tap -> long press:exit`: double tap
|
||||
the button for the previous message, tap once for the next, and hold to
|
||||
return home. Advancing past the last message also returns home. The hint
|
||||
appears even when the inbox is empty and reflows onto additional lines on
|
||||
narrower screens. It replaces the unusable channel selector on single-button
|
||||
builds. These are button taps. Touchscreen and joystick builds retain
|
||||
instructions appropriate to their controls. During the first eight
|
||||
seconds after startup, holding the button on an ordinary home page enters
|
||||
CLI rescue instead; wait for that startup window to finish before opening
|
||||
the inbox. Exiting a message preview and the WiFi setup page's hold action
|
||||
remain available immediately.
|
||||
|
||||
Larger display classes keep their existing font. Menus, Bluetooth PINs and WiFi
|
||||
setup QR codes keep their normal layout. Small TFTs retain their existing
|
||||
@@ -80,8 +97,9 @@ pio test -e native -f test_display_driver -f test_companion_message_history
|
||||
|
||||
The native tests exercise both capital heights, automatic font selection,
|
||||
160-character messages, complete rows at display edges, long sender lines,
|
||||
reserved footers, overflow markers and tiny/rotated screen geometry. The
|
||||
additional rendering comparison checks all 95 printable ASCII glyphs in each
|
||||
navigation hints, reserved footers, overflow markers and tiny/rotated screen
|
||||
geometry. The additional rendering comparison checks all 95 printable ASCII
|
||||
glyphs in each
|
||||
font, under address/undefined-behavior sanitizers. Picopixel is compared pixel
|
||||
for pixel with Adafruit GFX; Squeezed Regular 6 is compared with the upstream
|
||||
BDF fixture independently of the converted C++ tables. This needs a cached
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <helpers/ui/BluetoothPairingUiPolicy.h>
|
||||
#include <helpers/ui/CompanionHomeLayout.h>
|
||||
#include <helpers/ui/CompanionMessageHistory.h>
|
||||
#include <helpers/ui/ReaderNavigationHint.h>
|
||||
#include <helpers/ui/DisplayTextLayout.h>
|
||||
#if UI_SMALL_MESSAGE_FONT == 1
|
||||
#include <helpers/ui/SmallMessageText.h>
|
||||
#endif
|
||||
@@ -72,10 +74,22 @@ static uint64_t companionMessageElapsedMillis(uint64_t heard_millis) {
|
||||
#define UI_RECENT_LIST_SIZE 4
|
||||
#endif
|
||||
|
||||
#if UI_HAS_JOYSTICK || UI_HAS_ROTARY_INPUT
|
||||
#ifdef HAS_TOUCH
|
||||
#define PRESS_LABEL "tap center"
|
||||
#define SELECT_LABEL "TAP"
|
||||
#elif UI_HAS_JOYSTICK || defined(UI_HAS_NAV_INPUT)
|
||||
#define PRESS_LABEL "press Enter"
|
||||
#define SELECT_LABEL "ENTER"
|
||||
#else
|
||||
#define PRESS_LABEL "long press"
|
||||
#define PRESS_LABEL "hold button"
|
||||
#define SELECT_LABEL "HOLD"
|
||||
#endif
|
||||
|
||||
#if !defined(HAS_TOUCH) && !UI_HAS_JOYSTICK && !defined(UI_HAS_NAV_INPUT) \
|
||||
&& (defined(PIN_USER_BTN) || defined(PIN_USER_BTN_ANA))
|
||||
#define UI_BUTTON_READER_HINT 1
|
||||
#else
|
||||
#define UI_BUTTON_READER_HINT 0
|
||||
#endif
|
||||
|
||||
#ifdef COMPANION_EXCLUSIVE_WIFI_BLE
|
||||
@@ -278,7 +292,7 @@ static void drawCompanionWiFiSetupPage(DisplayDriver& display) {
|
||||
display.drawTextCentered(display.width() / 2, 45, "WIFI SETUP");
|
||||
display.setTextSize(2);
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
display.drawTextCentered(display.width() / 2, 90, "TAP TO START");
|
||||
display.drawTextCentered(display.width() / 2, 90, SELECT_LABEL " TO START");
|
||||
} else {
|
||||
display.setTextSize(3);
|
||||
display.drawTextCentered(display.width() / 2, 45, "WIFI");
|
||||
@@ -657,7 +671,7 @@ public:
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.info, layout.instruction_y,
|
||||
"tap center: inbox");
|
||||
PRESS_LABEL ": inbox");
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
if (!isCompanionWiFiEnabled()) {
|
||||
@@ -713,7 +727,7 @@ public:
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
display.setTextSize(3);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.pairing, layout.pairing_label_y, "TAP");
|
||||
display, layout.pairing, layout.pairing_label_y, SELECT_LABEL);
|
||||
#ifdef WIFI_SSID
|
||||
if (!isCompanionWiFiEnabled()) {
|
||||
strcpy(tmp, "OFF");
|
||||
@@ -776,7 +790,7 @@ public:
|
||||
display.setTextSize(1);
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
display.drawTextCentered(display.width() / 2, 43,
|
||||
"tap center: inbox");
|
||||
PRESS_LABEL ": inbox");
|
||||
|
||||
#ifdef UI_SHOW_CLOCK
|
||||
display.setTextSize(3);
|
||||
@@ -1206,8 +1220,10 @@ public:
|
||||
#ifndef UI_COMPACT_MESSAGE_STATUS
|
||||
#define UI_COMPACT_MESSAGE_STATUS 0
|
||||
#endif
|
||||
// Single-button readers cannot operate the up/down channel selector. Use
|
||||
// their footer space for the available navigation controls instead.
|
||||
#ifndef UI_MESSAGE_CHANNEL_FOOTER
|
||||
#if defined(MESHCORE_HAS_SMALL_DISPLAY)
|
||||
#if defined(MESHCORE_HAS_SMALL_DISPLAY) || UI_BUTTON_READER_HINT == 1
|
||||
#define UI_MESSAGE_CHANNEL_FOOTER 0
|
||||
#else
|
||||
#define UI_MESSAGE_CHANNEL_FOOTER 1
|
||||
@@ -1427,19 +1443,42 @@ public:
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
const mesh::ui::CompanionMessageChromeLayout layout =
|
||||
mesh::ui::CompanionMessageChromeLayout layout =
|
||||
mesh::ui::makeCompanionMessageChromeLayout(
|
||||
UI_COMPACT_MESSAGE_STATUS == 1);
|
||||
int body_bottom = UI_MESSAGE_CHANNEL_FOOTER == 1
|
||||
? display.height() - layout.filter_height : display.height();
|
||||
display.setCompactText(layout.compact_text);
|
||||
display.setTextSize(1);
|
||||
#if UI_BUTTON_READER_HINT == 1
|
||||
#if UI_SMALL_MESSAGE_FONT == 1
|
||||
mesh::ui::SmallMessageText reader_text(display);
|
||||
// Eight-pixel rows for the header and origin leave five message rows and
|
||||
// a navigation row on a 128x64 V4. Keep the 5px font on tiny panels.
|
||||
const int hint_line_height = reader_text.lineHeight();
|
||||
layout.header_divider_y = hint_line_height - 1;
|
||||
layout.origin_y = hint_line_height;
|
||||
layout.message_y = 2 * hint_line_height;
|
||||
#else
|
||||
DisplayDriver& reader_text = display;
|
||||
const int hint_line_height = layout.message_y - layout.origin_y;
|
||||
#endif
|
||||
const mesh::ui::ButtonReaderHintLayout hint =
|
||||
mesh::ui::makeButtonReaderHintLayout(
|
||||
reader_text, hint_line_height, body_bottom);
|
||||
body_bottom = hint.top;
|
||||
DisplayDriver& header = reader_text;
|
||||
#else
|
||||
DisplayDriver& header = display;
|
||||
#endif
|
||||
char tmp[24];
|
||||
int filtered_count = filteredCount();
|
||||
if (view_offset >= filtered_count) view_offset = 0;
|
||||
display.setCompactText(layout.compact_text);
|
||||
display.setCursor(0, 0);
|
||||
display.setTextSize(1);
|
||||
display.setColor(UIColor::corp_blue);
|
||||
header.setCursor(0, 0);
|
||||
header.setColor(UIColor::corp_blue);
|
||||
snprintf(tmp, sizeof(tmp), display.width() < 100 ? "%d/%d" : "Message %d/%d",
|
||||
filtered_count == 0 ? 0 : view_offset + 1, filtered_count);
|
||||
display.print(tmp);
|
||||
header.print(tmp);
|
||||
|
||||
const MsgEntry* p = filteredEntry(view_offset);
|
||||
|
||||
@@ -1447,8 +1486,16 @@ public:
|
||||
display.drawRect(0, layout.header_divider_y, display.width(), 1);
|
||||
display.setCompactText(false);
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
#if UI_BUTTON_READER_HINT == 1
|
||||
display.setCompactText(layout.compact_text);
|
||||
reader_text.drawTextEllipsized(0, layout.origin_y, display.width(),
|
||||
"No buffered messages");
|
||||
mesh::ui::drawButtonReaderHint(reader_text, hint);
|
||||
display.setCompactText(false);
|
||||
#else
|
||||
display.drawTextCentered(display.width() / 2, 40,
|
||||
"No buffered messages");
|
||||
#endif
|
||||
renderChannelFilter(display);
|
||||
return 5000;
|
||||
}
|
||||
@@ -1459,17 +1506,15 @@ public:
|
||||
char* suffix = strchr(tmp, ' ');
|
||||
if (suffix != nullptr) *suffix = 0;
|
||||
}
|
||||
display.setCursor(display.width() - display.getTextWidth(tmp) - 2, 0);
|
||||
display.print(tmp);
|
||||
header.setCursor(display.width() - header.getTextWidth(tmp) - 2, 0);
|
||||
header.print(tmp);
|
||||
|
||||
display.drawRect(0, layout.header_divider_y, display.width(), 1);
|
||||
display.setCompactText(false);
|
||||
|
||||
#if UI_SMALL_MESSAGE_FONT == 1
|
||||
mesh::ui::drawSmallMessageBody(display, p->origin, p->message,
|
||||
layout.origin_y,
|
||||
UI_MESSAGE_CHANNEL_FOOTER == 1
|
||||
? display.height() - layout.filter_height : display.height());
|
||||
layout.origin_y, body_bottom);
|
||||
#else
|
||||
display.setCursor(0, layout.origin_y);
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
@@ -1481,9 +1526,21 @@ public:
|
||||
display.setColor(UIColor::primary_txt);
|
||||
char filtered_msg[sizeof(p->message)];
|
||||
display.translateUTF8ToBlocks(filtered_msg, p->message, sizeof(filtered_msg));
|
||||
#if UI_BUTTON_READER_HINT == 1
|
||||
const int line_height = layout.message_y - layout.origin_y;
|
||||
const int message_rows = (body_bottom - layout.message_y) / line_height;
|
||||
mesh::ui::drawTextWrapped(display, 0, layout.message_y, display.width(),
|
||||
line_height, message_rows, filtered_msg);
|
||||
#else
|
||||
display.printWordWrap(filtered_msg, display.width());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if UI_BUTTON_READER_HINT == 1
|
||||
display.setCompactText(layout.compact_text);
|
||||
mesh::ui::drawButtonReaderHint(reader_text, hint);
|
||||
display.setCompactText(false);
|
||||
#endif
|
||||
renderChannelFilter(display);
|
||||
|
||||
#if AUTO_OFF_MILLIS==0 // probably e-ink
|
||||
@@ -2067,6 +2124,9 @@ char UITask::checkDisplayOn(char c) {
|
||||
}
|
||||
|
||||
char UITask::handleLongPress(char c) {
|
||||
// Exit an incoming message preview even during the startup rescue window.
|
||||
// The on-screen reader hint must not unexpectedly enter CLI rescue.
|
||||
if (curr == msg_preview) return c;
|
||||
#if UI_WIFI_SETUP_HOME_PAGE == 1
|
||||
// A setup AP may focus this page immediately at boot. Its documented HOLD
|
||||
// action must win over the otherwise-global early-boot CLI rescue gesture.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
namespace mesh {
|
||||
namespace ui {
|
||||
|
||||
struct ButtonReaderHintLayout {
|
||||
int top;
|
||||
int line_height;
|
||||
int line_count;
|
||||
const char* lines[3];
|
||||
};
|
||||
|
||||
// Match the single-button UI: double click is Previous, click is Next, and
|
||||
// hold is Enter/exit. Measure with the reader's font, including tiny panels.
|
||||
inline ButtonReaderHintLayout makeButtonReaderHintLayout(
|
||||
DisplayDriver& text, int line_height, int bottom) {
|
||||
ButtonReaderHintLayout layout = {
|
||||
0, line_height, 1, {"<- 2tap 1tap -> long press:exit", nullptr, nullptr}};
|
||||
if (text.getTextWidth(layout.lines[0]) > text.width()) {
|
||||
layout.line_count = 2;
|
||||
layout.lines[0] = "<- 2tap 1tap ->";
|
||||
layout.lines[1] = "long press:exit";
|
||||
if (text.getTextWidth(layout.lines[0]) > text.width()
|
||||
|| text.getTextWidth(layout.lines[1]) > text.width()) {
|
||||
layout.line_count = 3;
|
||||
layout.lines[0] = "<- 2tap";
|
||||
layout.lines[1] = "1tap ->";
|
||||
layout.lines[2] = "hold:exit";
|
||||
}
|
||||
}
|
||||
layout.top = bottom - layout.line_count * line_height;
|
||||
if (layout.top < 0) layout.top = 0;
|
||||
return layout;
|
||||
}
|
||||
|
||||
inline void drawButtonReaderHint(DisplayDriver& text,
|
||||
const ButtonReaderHintLayout& layout) {
|
||||
text.setColor(UIColor::window_bkg);
|
||||
text.fillRect(0, layout.top, text.width(),
|
||||
layout.line_count * layout.line_height);
|
||||
text.setColor(UIColor::secondary_txt);
|
||||
for (int row = 0; row < layout.line_count; ++row) {
|
||||
text.drawTextCentered(text.width() / 2,
|
||||
layout.top + row * layout.line_height,
|
||||
layout.lines[row]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace mesh
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <helpers/ui/CompanionHomeLayout.h>
|
||||
#include <helpers/ui/DisplayTextLayout.h>
|
||||
#include <helpers/ui/SmallMessageText.h>
|
||||
#include <helpers/ui/ReaderNavigationHint.h>
|
||||
#include <helpers/ui/WiFiSetupQrDisplay.h>
|
||||
#include <helpers/ui/WiFiSetupQrPayload.h>
|
||||
|
||||
@@ -233,6 +234,51 @@ TEST(SmallMessageText, MessageBodyRespectsReservedFooter) {
|
||||
for (const auto& pixel : display.fills) EXPECT_LT(pixel.y, 56);
|
||||
}
|
||||
|
||||
TEST(SmallMessageText, ButtonHintKeepsFiveMessageRowsWithoutOverlappingText) {
|
||||
TestDisplay display(128, 64);
|
||||
mesh::ui::SmallMessageText text(display);
|
||||
const auto hint = mesh::ui::makeButtonReaderHintLayout(
|
||||
text, text.lineHeight(), display.height());
|
||||
ASSERT_EQ(1, hint.line_count);
|
||||
EXPECT_EQ(56, hint.top);
|
||||
EXPECT_EQ(5, text.lineCount(2 * text.lineHeight(), hint.top));
|
||||
|
||||
mesh::ui::drawSmallMessageBody(display, "Ch 0 Public [4h]:",
|
||||
std::string(160, 'W').c_str(),
|
||||
text.lineHeight(), hint.top);
|
||||
ASSERT_FALSE(display.fills.empty());
|
||||
for (const auto& pixel : display.fills) EXPECT_LT(pixel.y, hint.top);
|
||||
display.fills.clear();
|
||||
mesh::ui::drawButtonReaderHint(text, hint);
|
||||
ASSERT_FALSE(display.fills.empty());
|
||||
for (const auto& pixel : display.fills) {
|
||||
EXPECT_GE(pixel.x, 0);
|
||||
EXPECT_LE(pixel.x + pixel.width, display.width());
|
||||
EXPECT_GE(pixel.y, hint.top);
|
||||
EXPECT_LE(pixel.y + pixel.height, display.height());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SmallMessageText, ButtonHintReflowsOnTinyAndRotatedScreens) {
|
||||
for (auto dimensions : {std::pair<int, int>{72, 40}, {40, 72},
|
||||
{128, 32}, {32, 128}, {64, 128}, {128, 64}}) {
|
||||
TestDisplay display(dimensions.first, dimensions.second);
|
||||
mesh::ui::SmallMessageText text(display);
|
||||
const auto hint = mesh::ui::makeButtonReaderHintLayout(
|
||||
text, text.lineHeight(), display.height());
|
||||
for (int row = 0; row < hint.line_count; ++row)
|
||||
EXPECT_LE(text.getTextWidth(hint.lines[row]), display.width());
|
||||
mesh::ui::drawButtonReaderHint(text, hint);
|
||||
ASSERT_FALSE(display.fills.empty());
|
||||
for (const auto& pixel : display.fills) {
|
||||
EXPECT_GE(pixel.x, 0);
|
||||
EXPECT_LE(pixel.x + pixel.width, display.width());
|
||||
EXPECT_GE(pixel.y, hint.top);
|
||||
EXPECT_LE(pixel.y + pixel.height, display.height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SmallMessageText, Full160CharacterSamplesFitInFiveRows) {
|
||||
class RecordingText : public mesh::ui::SmallMessageText {
|
||||
public:
|
||||
@@ -257,6 +303,15 @@ TEST(SmallMessageText, Full160CharacterSamplesFitInFiveRows) {
|
||||
EXPECT_EQ(5, mesh::ui::drawTextWrapped(text, 0, 22, 128,
|
||||
text.lineHeight(), text.lineCount(22), sample));
|
||||
EXPECT_EQ(sample, text.drawn);
|
||||
// The button reader moves its header/origin up to reserve the hint, so
|
||||
// the same complete 160-character text still fits in five body rows.
|
||||
text.drawn.clear();
|
||||
const auto hint = mesh::ui::makeButtonReaderHintLayout(
|
||||
text, text.lineHeight(), display.height());
|
||||
const int message_y = 2 * text.lineHeight();
|
||||
EXPECT_EQ(5, mesh::ui::drawTextWrapped(text, 0, message_y, 128,
|
||||
text.lineHeight(), text.lineCount(message_y, hint.top), sample));
|
||||
EXPECT_EQ(sample, text.drawn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
@@ -141,6 +143,60 @@ class HeltecV4WiFiSetupPageTest(unittest.TestCase):
|
||||
self.assertNotIn("savePrefs", loop[stop:state_service])
|
||||
self.assertNotIn("saveEnabled", loop[stop:state_service])
|
||||
|
||||
def test_reader_exit_wins_over_startup_rescue_but_home_keeps_rescue(self):
|
||||
ui = UI.read_text(encoding="utf-8")
|
||||
start = ui.index("char UITask::handleLongPress(char c) {")
|
||||
end = ui.index("\nchar UITask::handleDoubleClick", start)
|
||||
source = r'''
|
||||
#include <cassert>
|
||||
#define UI_WIFI_SETUP_HOME_PAGE 1
|
||||
unsigned long now = 4000;
|
||||
unsigned long millis() { return now; }
|
||||
struct Screen {};
|
||||
struct HomeScreen : Screen {
|
||||
bool setup = false;
|
||||
bool isWiFiSetupPage() const { return setup; }
|
||||
};
|
||||
struct Mesh {
|
||||
int rescue_calls = 0;
|
||||
void enterCLIRescue() { ++rescue_calls; }
|
||||
} the_mesh;
|
||||
struct UITask {
|
||||
Screen* curr;
|
||||
Screen* home;
|
||||
Screen* msg_preview;
|
||||
unsigned long ui_started_at = 0;
|
||||
char handleLongPress(char c);
|
||||
};
|
||||
''' + ui[start:end] + r'''
|
||||
int main() {
|
||||
HomeScreen home;
|
||||
Screen reader;
|
||||
UITask task{&reader, &home, &reader};
|
||||
assert(task.handleLongPress('E') == 'E');
|
||||
assert(the_mesh.rescue_calls == 0);
|
||||
task.curr = &home;
|
||||
assert(task.handleLongPress('E') == 0);
|
||||
assert(the_mesh.rescue_calls == 1);
|
||||
home.setup = true;
|
||||
assert(task.handleLongPress('E') == 'E');
|
||||
assert(the_mesh.rescue_calls == 1);
|
||||
home.setup = false;
|
||||
now = 9000;
|
||||
assert(task.handleLongPress('E') == 'E');
|
||||
task.curr = &reader;
|
||||
assert(task.handleLongPress('E') == 'E');
|
||||
assert(the_mesh.rescue_calls == 1);
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
executable = Path(temp_dir) / "reader_hold"
|
||||
subprocess.run(
|
||||
["c++", "-std=c++17", "-x", "c++", "-", "-o", str(executable)],
|
||||
input=source, text=True, capture_output=True, check=True,
|
||||
)
|
||||
subprocess.run([str(executable)], check=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -78,7 +78,7 @@ class T096MessageFooterProfileTest(unittest.TestCase):
|
||||
ui = UI.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"#ifndef UI_MESSAGE_CHANNEL_FOOTER\n"
|
||||
" #if defined(MESHCORE_HAS_SMALL_DISPLAY)\n"
|
||||
" #if defined(MESHCORE_HAS_SMALL_DISPLAY) || UI_BUTTON_READER_HINT == 1\n"
|
||||
" #define UI_MESSAGE_CHANNEL_FOOTER 0\n"
|
||||
" #else\n"
|
||||
" #define UI_MESSAGE_CHANNEL_FOOTER 1\n"
|
||||
|
||||
Reference in new issue
Block a user