A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: keyboard feedback #1644 (#1697)

Addresses reviewer feedback from #1644:
- **Localize keyboard hint strings** — 13 hardcoded English strings
replaced with \`tr()\` macro (\`STR_KB_HINT_*\`), making them
translatable across all 22 languages (fallback to English when not yet
translated)
- **Deduplicate \`Lyra3CoversMetrics\`** — now derives from
\`LyraMetrics\` via lambda copy, overriding only \`homeCoverTileHeight\`
and \`homeRecentBooksCount\` (eliminates ~30 duplicated metric fields)
- **Unify keyboard drawing in \`BaseTheme\`** — \`drawTextField\` and
\`drawKeyboardKey\` overrides removed from \`LyraTheme\`; variability
controlled via \`keyboardKeyCornerRadius\` metric (0=Base, 6=Lyra).
Unified text field padding to 6, adopted Lyra's secondary label draw
order (main first, then secondary)
- **Add URL-optimized keyboard layout** — \`urlLayout\` with \`:\` and
\`/\` replacing \`=\` and \`,\` for easier URL input without switching
to SYM mode
---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _** YES **_

authored by

pablohc and committed by
GitHub
1a145fe0 302dea1e

+123 -140
+15
lib/I18n/translations/english.yaml
··· 298 298 STR_CRASH_DESCRIPTION: "A detailed report was saved to crash_report.txt. Please include this file in your bug report." 299 299 STR_CRASH_REASON: "Crash reason:" 300 300 STR_CRASH_NO_REASON: "(No reason was recorded)" 301 + STR_KB_HINT_MOVE_CURSOR: "Press LEFT or RIGHT to move cursor" 302 + STR_KB_HINT_RETURN_CURSOR: "Press LEFT to return to cursor position" 303 + STR_KB_HINT_HIDE_PASSWORD: "Hold RIGHT then press [***] to hide password" 304 + STR_KB_HINT_SHOW_PASSWORD: "Hold RIGHT then press [abc] to show password" 305 + STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Press [***] to hide password" 306 + STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Press [abc] to show password" 307 + STR_KB_HINT_EDIT_ENTRY: "Hold UP to edit entry" 308 + STR_KB_TIPS: "Tips:" 309 + STR_KB_HINT_RETURN_KEYBOARD: "Press DOWN to return to keyboard" 310 + STR_KB_HINT_EXIT_URL_MODE: "Press ABC to exit URL mode" 311 + STR_KB_HINT_CLEAR_TEXT: "Hold DEL to clear all text" 312 + STR_KB_HINT_SECONDARY_CHAR: "Hold SELECT for secondary char" 313 + STR_KB_HINT_UPPER_SECONDARY: "Hold SELECT for UPPERCASE or secondary char" 314 + STR_KB_HINT_LOWER_SECONDARY: "Hold SELECT for lowercase or secondary char" 315 + STR_KB_HINT_URL_SNIPPETS: "Press URL for snippets"
+25 -23
src/activities/util/KeyboardEntryActivity.cpp
··· 49 49 bool KeyboardEntryActivity::isBottomRow(const int row) const { return row == getContentRowCount(); } 50 50 51 51 char KeyboardEntryActivity::getSelectedChar() const { 52 - const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout; 52 + const KeyDef(*layout)[COLS] = symMode ? symLayout : (inputType == InputType::Url ? urlLayout : abcLayout); 53 53 54 54 if (selectedRow < 0 || selectedRow >= getContentRowCount()) return '\0'; 55 55 if (selectedCol < 0 || selectedCol >= COLS) return '\0'; ··· 526 526 const int hintY = underlineY + 4; 527 527 if (cursorMode) { 528 528 int hintLineY = hintY; 529 - renderer.drawCenteredText(SMALL_FONT_ID, hintLineY, "Press < or > to move cursor", true); 530 - hintLineY += hintLh; 531 - if (inputType == InputType::Password) { 532 - const char* passTip; 533 - if (togglePos) { 534 - passTip = "Press < to return to cursor position"; 535 - } else { 536 - passTip = 537 - passwordVisible ? "Hold > then press [***] to hide password" : "Hold > then press [abc] to show password"; 529 + if (inputType == InputType::Password && togglePos) { 530 + renderer.drawCenteredText( 531 + SMALL_FONT_ID, hintLineY, 532 + passwordVisible ? tr(STR_KB_HINT_TOGGLE_HIDE_PASSWORD) : tr(STR_KB_HINT_TOGGLE_SHOW_PASSWORD), true); 533 + hintLineY += hintLh; 534 + renderer.drawCenteredText(SMALL_FONT_ID, hintLineY, tr(STR_KB_HINT_RETURN_CURSOR), true); 535 + } else { 536 + renderer.drawCenteredText(SMALL_FONT_ID, hintLineY, tr(STR_KB_HINT_MOVE_CURSOR), true); 537 + hintLineY += hintLh; 538 + if (inputType == InputType::Password) { 539 + const char* passTip = passwordVisible ? tr(STR_KB_HINT_HIDE_PASSWORD) : tr(STR_KB_HINT_SHOW_PASSWORD); 540 + renderer.drawCenteredText(SMALL_FONT_ID, hintLineY, passTip, true); 538 541 } 539 - renderer.drawCenteredText(SMALL_FONT_ID, hintLineY, passTip, true); 540 542 } 541 543 } else { 542 - renderer.drawCenteredText(SMALL_FONT_ID, hintY, "Hold UP to edit entry", true); 544 + renderer.drawCenteredText(SMALL_FONT_ID, hintY, tr(STR_KB_HINT_EDIT_ENTRY), true); 543 545 } 544 546 } 545 547 ··· 575 577 576 578 if (tipCount > 0) { 577 579 int y = (underlineBottom + keyboardStartY) / 2 - (tipCount + 1) * tipsLh / 2; 578 - drawTip("Tips:", y); 580 + drawTip(tr(STR_KB_TIPS), y); 579 581 y += tipsLh; 580 582 if (cursorMode) { 581 - drawTip("Press DOWN to return to keyboard", y); 583 + drawTip(tr(STR_KB_HINT_RETURN_KEYBOARD), y); 582 584 } else if (urlMode) { 583 - drawTip("Press ABC to exit URL mode", y); 585 + drawTip(tr(STR_KB_HINT_EXIT_URL_MODE), y); 584 586 y += tipsLh; 585 587 if (!text.empty()) { 586 - drawTip("Hold DEL to clear all text", y); 588 + drawTip(tr(STR_KB_HINT_CLEAR_TEXT), y); 587 589 } 588 590 } else if (symMode) { 589 591 if (!text.empty()) { 590 - drawTip("Hold DEL to clear all text", y); 592 + drawTip(tr(STR_KB_HINT_CLEAR_TEXT), y); 591 593 } 592 594 } else { 593 595 const char* altCharTip; 594 596 if (inputType == InputType::Url) { 595 - altCharTip = "Hold SELECT for secondary char"; 597 + altCharTip = tr(STR_KB_HINT_SECONDARY_CHAR); 596 598 } else if (shiftState > 0) { 597 - altCharTip = "Hold SELECT for lowercase or secondary char"; 599 + altCharTip = tr(STR_KB_HINT_LOWER_SECONDARY); 598 600 } else { 599 - altCharTip = "Hold SELECT for UPPERCASE or secondary char"; 601 + altCharTip = tr(STR_KB_HINT_UPPER_SECONDARY); 600 602 } 601 603 drawTip(altCharTip, y); 602 604 y += tipsLh; 603 605 if (inputType == InputType::Url) { 604 - drawTip("Press URL for snippets", y); 606 + drawTip(tr(STR_KB_HINT_URL_SNIPPETS), y); 605 607 y += tipsLh; 606 608 } 607 609 if (!text.empty()) { 608 - drawTip("Hold DEL to clear all text", y); 610 + drawTip(tr(STR_KB_HINT_CLEAR_TEXT), y); 609 611 } 610 612 } 611 613 } ··· 625 627 urlLeftMargin = urlCenterX - urlTotalWidth / 2; 626 628 } 627 629 628 - const KeyDef(*layout)[COLS] = symMode ? symLayout : abcLayout; 630 + const KeyDef(*layout)[COLS] = symMode ? symLayout : (inputType == InputType::Url ? urlLayout : abcLayout); 629 631 const int contentRows = getContentRowCount(); 630 632 631 633 for (int row = 0; row < contentRows; row++) {
+43
src/activities/util/KeyboardEntryActivity.h
··· 125 125 {',', '<'}}, 126 126 }; 127 127 128 + static constexpr KeyDef urlLayout[ABC_ROWS][COLS] = { 129 + {{'1', '!'}, 130 + {'2', '@'}, 131 + {'3', '#'}, 132 + {'4', '$'}, 133 + {'5', '%'}, 134 + {'6', '^'}, 135 + {'7', '&'}, 136 + {'8', '*'}, 137 + {'9', '('}, 138 + {'0', ')'}}, 139 + {{'q', 'Q'}, 140 + {'w', 'W'}, 141 + {'e', 'E'}, 142 + {'r', 'R'}, 143 + {'t', 'T'}, 144 + {'y', 'Y'}, 145 + {'u', 'U'}, 146 + {'i', 'I'}, 147 + {'o', 'O'}, 148 + {'p', 'P'}}, 149 + {{'a', 'A'}, 150 + {'s', 'S'}, 151 + {'d', 'D'}, 152 + {'f', 'F'}, 153 + {'g', 'G'}, 154 + {'h', 'H'}, 155 + {'j', 'J'}, 156 + {'k', 'K'}, 157 + {'l', 'L'}, 158 + {'-', '_'}}, 159 + {{'z', 'Z'}, 160 + {'x', 'X'}, 161 + {'c', 'C'}, 162 + {'v', 'V'}, 163 + {'b', 'B'}, 164 + {'n', 'N'}, 165 + {'m', 'M'}, 166 + {':', '+'}, 167 + {'.', '>'}, 168 + {'/', '<'}}, 169 + }; 170 + 128 171 static constexpr KeyDef symLayout[SYM_ROWS][COLS] = { 129 172 {{'1', '\0'}, 130 173 {'2', '\0'},
+28 -8
src/components/themes/BaseTheme.cpp
··· 783 783 784 784 void BaseTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode, 785 785 int contentStartX, int contentWidth) const { 786 + const auto& metrics = UITheme::getInstance().getMetrics(); 786 787 const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID); 787 - const int lineY = rect.y + rect.height + lineHeight + BaseMetrics::values.verticalSpacing; 788 + const int lineY = rect.y + rect.height + lineHeight + metrics.verticalSpacing; 788 789 const int thickness = cursorMode ? 3 : 1; 789 790 if (contentWidth > 0) { 790 791 renderer.drawLine(rect.x + contentStartX, lineY, rect.x + contentStartX + contentWidth, lineY, thickness, true); 791 792 } else { 792 - const int hPadding = 4; 793 + const int hPadding = 6; 793 794 const int lineW = textWidth + hPadding * 2; 794 795 renderer.drawLine(rect.x + (rect.width - lineW) / 2, lineY, rect.x + (rect.width + lineW) / 2, lineY, thickness, 795 796 true); ··· 799 800 void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected, 800 801 const char* secondaryLabel, const KeyboardKeyType keyType, 801 802 const bool inactiveSelection) const { 803 + const auto& metrics = UITheme::getInstance().getMetrics(); 804 + const int cr = metrics.keyboardKeyCornerRadius; 805 + 802 806 if (isSelected) { 803 807 if (inactiveSelection) { 804 - renderer.drawRect(rect.x, rect.y, rect.width, rect.height, 2, true); 808 + if (cr > 0) { 809 + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cr, Color::LightGray); 810 + } else { 811 + renderer.drawRect(rect.x, rect.y, rect.width, rect.height, 2, true); 812 + } 805 813 } else if (keyType == KeyboardKeyType::Disabled) { 806 - renderer.fillRectDither(rect.x, rect.y, rect.width, rect.height, Color::LightGray); 814 + if (cr > 0) { 815 + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cr, Color::LightGray); 816 + } else { 817 + renderer.fillRectDither(rect.x, rect.y, rect.width, rect.height, Color::LightGray); 818 + } 807 819 } else { 808 - renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true); 820 + if (cr > 0) { 821 + renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cr, Color::Black); 822 + } else { 823 + renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true); 824 + } 809 825 } 810 826 } else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode || keyType == KeyboardKeyType::Del || 811 827 keyType == KeyboardKeyType::Space || keyType == KeyboardKeyType::Ok || 812 828 keyType == KeyboardKeyType::Disabled) { 813 - renderer.drawRect(rect.x, rect.y, rect.width, rect.height); 829 + if (cr > 0) { 830 + renderer.drawRoundedRect(rect.x, rect.y, rect.width, rect.height, 1, cr, true); 831 + } else { 832 + renderer.drawRect(rect.x, rect.y, rect.width, rect.height); 833 + } 814 834 } 815 835 816 836 const bool invert = isSelected && !inactiveSelection; ··· 841 861 const int textX = rect.x + (rect.width - itemWidth) / 2; 842 862 const int textY = rect.y + (rect.height - renderer.getLineHeight(UI_12_FONT_ID)) / 2; 843 863 864 + renderer.drawText(UI_12_FONT_ID, textX, textY, label, !invert); 865 + 844 866 if (hasSecondary) { 845 867 const int secWidth = renderer.getTextWidth(SMALL_FONT_ID, secondaryLabel); 846 868 renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - secWidth - 1, rect.y, secondaryLabel, !invert); 847 869 } 848 - 849 - renderer.drawText(UI_12_FONT_ID, textX, textY, label, !invert); 850 870 }
+3 -1
src/components/themes/BaseTheme.h
··· 67 67 int keyboardVerticalOffset; 68 68 int keyboardTextFieldWidthPercent; 69 69 int keyboardWidthPercent; 70 + int keyboardKeyCornerRadius; 70 71 }; 71 72 72 73 enum UIIcon { Folder, Text, Image, Book, File, Recent, Settings, Transfer, Library, Wifi, Hotspot }; ··· 111 112 .keyboardCenteredText = false, 112 113 .keyboardVerticalOffset = -13, 113 114 .keyboardTextFieldWidthPercent = 85, 114 - .keyboardWidthPercent = 90}; 115 + .keyboardWidthPercent = 90, 116 + .keyboardKeyCornerRadius = 0}; 115 117 } 116 118 117 119 class BaseTheme {
+7 -37
src/components/themes/lyra/Lyra3CoversTheme.h
··· 6 6 7 7 class GfxRenderer; 8 8 9 - // Lyra theme metrics (zero runtime cost) 10 9 namespace Lyra3CoversMetrics { 11 - constexpr ThemeMetrics values = {.batteryWidth = 16, 12 - .batteryHeight = 12, 13 - .topPadding = 5, 14 - .batteryBarHeight = 40, 15 - .headerHeight = 84, 16 - .verticalSpacing = 16, 17 - .contentSidePadding = 20, 18 - .listRowHeight = 40, 19 - .listWithSubtitleRowHeight = 60, 20 - .menuRowHeight = 64, 21 - .menuSpacing = 8, 22 - .tabSpacing = 8, 23 - .tabBarHeight = 40, 24 - .scrollBarWidth = 4, 25 - .scrollBarRightOffset = 5, 26 - .homeTopPadding = 56, 27 - .homeCoverHeight = 226, 28 - .homeCoverTileHeight = 300, 29 - .homeRecentBooksCount = 3, 30 - .buttonHintsHeight = 40, 31 - .sideButtonHintsWidth = 30, 32 - .progressBarHeight = 16, 33 - .progressBarMarginTop = 1, 34 - .statusBarHorizontalMargin = 5, 35 - .statusBarVerticalMargin = 19, 36 - .keyboardKeyWidth = 31, 37 - .keyboardKeyHeight = 40, 38 - .keyboardKeySpacing = 0, 39 - .keyboardBottomKeyHeight = 35, 40 - .keyboardBottomKeySpacing = 5, 41 - .keyboardBottomAligned = true, 42 - .keyboardCenteredText = false, 43 - .keyboardVerticalOffset = -7, 44 - .keyboardTextFieldWidthPercent = 85, 45 - .keyboardWidthPercent = 90}; 46 - } 10 + constexpr ThemeMetrics values = [] { 11 + ThemeMetrics v = LyraMetrics::values; 12 + v.homeCoverTileHeight = 300; 13 + v.homeRecentBooksCount = 3; 14 + return v; 15 + }(); 16 + } // namespace Lyra3CoversMetrics 47 17 48 18 class Lyra3CoversTheme : public LyraTheme { 49 19 public:
-65
src/components/themes/lyra/LyraTheme.cpp
··· 577 577 578 578 renderer.displayBuffer(HalDisplay::FAST_REFRESH); 579 579 } 580 - 581 - void LyraTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode, 582 - int contentStartX, int contentWidth) const { 583 - int lineY = rect.y + rect.height + renderer.getLineHeight(UI_12_FONT_ID) + LyraMetrics::values.verticalSpacing; 584 - const int thickness = cursorMode ? 3 : 1; 585 - if (contentWidth > 0) { 586 - renderer.drawLine(rect.x + contentStartX, lineY, rect.x + contentStartX + contentWidth, lineY, thickness, true); 587 - } else { 588 - int lineW = textWidth + hPaddingInSelection * 2; 589 - renderer.drawLine(rect.x + (rect.width - lineW) / 2, lineY, rect.x + (rect.width + lineW) / 2, lineY, thickness, 590 - true); 591 - } 592 - } 593 - 594 - void LyraTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected, 595 - const char* secondaryLabel, const KeyboardKeyType keyType, 596 - const bool inactiveSelection) const { 597 - if (isSelected) { 598 - if (inactiveSelection) { 599 - renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray); 600 - } else if (keyType == KeyboardKeyType::Disabled) { 601 - renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray); 602 - } else { 603 - renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::Black); 604 - } 605 - } else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode || keyType == KeyboardKeyType::Del || 606 - keyType == KeyboardKeyType::Space || keyType == KeyboardKeyType::Ok || 607 - keyType == KeyboardKeyType::Disabled) { 608 - renderer.drawRoundedRect(rect.x, rect.y, rect.width, rect.height, 1, cornerRadius, true); 609 - } 610 - 611 - const bool invert = isSelected && !inactiveSelection; 612 - 613 - if (keyType == KeyboardKeyType::Space) { 614 - const int lineHalfWidth = rect.width * 3 / 10; 615 - const int centerX = rect.x + rect.width / 2; 616 - const int lineY = rect.y + rect.height / 2 + 3; 617 - renderer.drawLine(centerX - lineHalfWidth, lineY, centerX + lineHalfWidth, lineY, 3, !invert); 618 - return; 619 - } 620 - 621 - if (keyType == KeyboardKeyType::Del) { 622 - const int centerX = rect.x + rect.width / 2; 623 - const int centerY = rect.y + rect.height / 2; 624 - const int arrowLen = rect.width / 4; 625 - const int arrowHead = arrowLen / 2; 626 - renderer.drawLine(centerX - arrowLen / 2, centerY, centerX + arrowLen / 2, centerY, 3, !invert); 627 - renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY - arrowHead, 3, 628 - !invert); 629 - renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY + arrowHead, 3, 630 - !invert); 631 - return; 632 - } 633 - 634 - const bool hasSecondary = secondaryLabel != nullptr && secondaryLabel[0] != '\0'; 635 - const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, label); 636 - const int textX = rect.x + (rect.width - textWidth) / 2; 637 - const int textY = rect.y + (rect.height - renderer.getLineHeight(UI_12_FONT_ID)) / 2; 638 - renderer.drawText(UI_12_FONT_ID, textX, textY, label, !invert); 639 - 640 - if (hasSecondary) { 641 - const int secWidth = renderer.getTextWidth(SMALL_FONT_ID, secondaryLabel); 642 - renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - secWidth - 1, rect.y, secondaryLabel, !invert); 643 - } 644 - }
+2 -6
src/components/themes/lyra/LyraTheme.h
··· 40 40 .keyboardCenteredText = false, 41 41 .keyboardVerticalOffset = -7, 42 42 .keyboardTextFieldWidthPercent = 85, 43 - .keyboardWidthPercent = 90}; 43 + .keyboardWidthPercent = 90, 44 + .keyboardKeyCornerRadius = 6}; 44 45 } 45 46 46 47 class LyraTheme : public BaseTheme { ··· 71 72 void drawEmptyRecents(const GfxRenderer& renderer, const Rect rect) const; 72 73 Rect drawPopup(const GfxRenderer& renderer, const char* message) const override; 73 74 void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const override; 74 - void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false, 75 - int contentStartX = 0, int contentWidth = 0) const override; 76 - void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected, 77 - const char* secondaryLabel = nullptr, KeyboardKeyType keyType = KeyboardKeyType::Normal, 78 - bool inactiveSelection = false) const override; 79 75 bool showsFileIcons() const override { return true; } 80 76 };