From 172d8b49b92f2ef5aed8785084d9488d10340a28 Mon Sep 17 00:00:00 2001 From: Nicolas Roggeman Date: Wed, 16 Oct 2024 13:53:37 +0200 Subject: [PATCH] Add extension to INFOS_LIST to enable alias for some info types --- lib_nbgl/include/nbgl_content.h | 9 +- lib_nbgl/include/nbgl_layout.h | 5 + lib_nbgl/src/nbgl_layout.c | 200 ++++++++++++++++++++------------ lib_nbgl/src/nbgl_page.c | 18 ++- lib_nbgl/src/nbgl_use_case.c | 62 ++++++---- 5 files changed, 194 insertions(+), 100 deletions(-) diff --git a/lib_nbgl/include/nbgl_content.h b/lib_nbgl/include/nbgl_content.h index ee68f265f..0c06616ae 100644 --- a/lib_nbgl/include/nbgl_content.h +++ b/lib_nbgl/include/nbgl_content.h @@ -266,7 +266,14 @@ typedef struct nbgl_pageSwitchesList_s { typedef struct { const char *const *infoTypes; ///< array of types of infos (in black/bold) const char *const *infoContents; ///< array of contents of infos (in black) - uint8_t nbInfos; ///< number of elements in infoTypes and infoContents array + const nbgl_contentValueExt_t + *infoExtensions; ///< if not NULL, gives additional info on type field + ///< any {0} element of this array is considered as invalid + uint8_t nbInfos; ///< number of elements in infoTypes and infoContents array + uint8_t token; ///< token to use with extensions, if withExtensions is true and infoExtensions + ///< is not NULL + bool withExtensions; /// if set to TRUE and if infoExtensions is not NULL, use this + /// infoExtensions field } nbgl_contentInfoList_t; /** diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h index 0fd7dca6d..c414ed91a 100644 --- a/lib_nbgl/include/nbgl_layout.h +++ b/lib_nbgl/include/nbgl_layout.h @@ -593,6 +593,11 @@ int nbgl_layoutAddTopRightButton(nbgl_layout_t *layout, int nbgl_layoutAddTouchableBar(nbgl_layout_t *layout, const nbgl_layoutBar_t *barLayout); int nbgl_layoutAddSwitch(nbgl_layout_t *layout, const nbgl_layoutSwitch_t *switchLayout); int nbgl_layoutAddText(nbgl_layout_t *layout, const char *text, const char *subText); +int nbgl_layoutAddTextWithAlias(nbgl_layout_t *layout, + const char *text, + const char *subText, + uint8_t token, + uint8_t index); int nbgl_layoutAddSubHeaderText(nbgl_layout_t *layout, const char *text); int nbgl_layoutAddRadioChoice(nbgl_layout_t *layout, const nbgl_layoutRadioChoice_t *choices); int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info); diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c index 179716f86..fdfd8914a 100644 --- a/lib_nbgl/src/nbgl_layout.c +++ b/lib_nbgl/src/nbgl_layout.c @@ -878,6 +878,109 @@ static nbgl_container_t *addContentCenter(nbgl_layoutInternal_t *layoutInt, return container; } +static int addText(nbgl_layout_t *layout, + const char *text, + const char *subText, + uint8_t token, + uint8_t index, + bool withAlias) +{ + nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_container_t *container; + nbgl_text_area_t *textArea; + nbgl_text_area_t *subTextArea; + uint16_t fullHeight = 0; + + if (layout == NULL) { + return -1; + } + container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + + // get container children + container->children = nbgl_containerPoolGet(withAlias ? 3 : 2, layoutInt->layer); + container->obj.area.width = AVAILABLE_WIDTH; + + if (text != NULL) { + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + + textArea->textColor = BLACK; + textArea->text = PIC(text); + textArea->textAlignment = MID_LEFT; + textArea->fontId = SMALL_BOLD_FONT; + textArea->style = NO_STYLE; + textArea->wrapping = true; + textArea->obj.alignment = NO_ALIGNMENT; + textArea->obj.alignmentMarginY = PRE_TEXT_MARGIN; + textArea->obj.area.width = container->obj.area.width; + if (withAlias == true) { + textArea->obj.area.width -= 12 + MINI_PUSH_ICON.width; + } + textArea->obj.area.height = nbgl_getTextHeightInWidth( + textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); + fullHeight += textArea->obj.area.height + textArea->obj.alignmentMarginY; + container->children[container->nbChildren] = (nbgl_obj_t *) textArea; + container->nbChildren++; + if (withAlias == true) { + nbgl_image_t *image = (nbgl_image_t *) nbgl_objPoolGet(IMAGE, layoutInt->layer); + layoutObj_t *obj + = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) image, token, TUNE_TAP_CASUAL); + obj->index = index; + image->foregroundColor = BLACK; + image->buffer = &MINI_PUSH_ICON; + image->obj.alignment = RIGHT_TOP; + image->obj.alignmentMarginX = 12; + image->obj.alignTo = (nbgl_obj_t *) textArea; + image->obj.touchMask = (1 << TOUCHED); + image->obj.touchId = VALUE_BUTTON_1_ID + index; + + container->children[container->nbChildren] = (nbgl_obj_t *) image; + container->nbChildren++; + } + } + if (subText != NULL) { + subTextArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + subTextArea->textColor = BLACK; + subTextArea->text = PIC(subText); + subTextArea->fontId = SMALL_REGULAR_FONT; + subTextArea->style = NO_STYLE; + subTextArea->wrapping = true; + subTextArea->obj.area.width = container->obj.area.width; + subTextArea->obj.area.height = nbgl_getTextHeightInWidth(subTextArea->fontId, + subTextArea->text, + subTextArea->obj.area.width, + subTextArea->wrapping); + subTextArea->textAlignment = MID_LEFT; + subTextArea->obj.alignment = NO_ALIGNMENT; + if (text != NULL) { + subTextArea->obj.alignmentMarginY = TEXT_SUBTEXT_MARGIN; + fullHeight += POST_SUBTEXT_MARGIN; // under the subText + } + else { +#ifdef TARGET_STAX + subTextArea->obj.alignmentMarginY = BORDER_MARGIN; + fullHeight += BORDER_MARGIN; +#else // TARGET_STAX + subTextArea->obj.alignmentMarginY = 26; + fullHeight += 26; // under the subText +#endif // TARGET_STAX + } + container->children[container->nbChildren] = (nbgl_obj_t *) subTextArea; + container->nbChildren++; + fullHeight += subTextArea->obj.area.height + subTextArea->obj.alignmentMarginY; + } + else { + fullHeight += PRE_TEXT_MARGIN; + } + container->obj.area.height = fullHeight; + container->layout = VERTICAL; + container->obj.alignmentMarginX = BORDER_MARGIN; + container->obj.alignment = NO_ALIGNMENT; + // set this new obj as child of main container + layoutAddObject(layoutInt, (nbgl_obj_t *) container); + + return container->obj.area.height; +} + /********************** * GLOBAL FUNCTIONS **********************/ @@ -1187,7 +1290,7 @@ int nbgl_layoutAddSwitch(nbgl_layout_t *layout, const nbgl_layoutSwitch_t *switc } /** - * @brief Creates an area with given text and sub text (in gray) + * @brief Creates an area with given text (in bold) and sub text (in regular) * * @param layout the current layout * @param text main text (in small bold font), optional @@ -1196,82 +1299,29 @@ int nbgl_layoutAddSwitch(nbgl_layout_t *layout, const nbgl_layoutSwitch_t *switc */ int nbgl_layoutAddText(nbgl_layout_t *layout, const char *text, const char *subText) { - nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; - nbgl_container_t *container; - nbgl_text_area_t *textArea; - nbgl_text_area_t *subTextArea; - uint16_t fullHeight = 0; - LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddText():\n"); - if (layout == NULL) { - return -1; - } - container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); - - // get container children - container->children = nbgl_containerPoolGet(2, layoutInt->layer); - container->obj.area.width = AVAILABLE_WIDTH; - - if (text != NULL) { - textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); - - textArea->textColor = BLACK; - textArea->text = PIC(text); - textArea->textAlignment = MID_LEFT; - textArea->fontId = SMALL_BOLD_FONT; - textArea->style = NO_STYLE; - textArea->wrapping = true; - textArea->obj.alignment = NO_ALIGNMENT; - textArea->obj.alignmentMarginY = PRE_TEXT_MARGIN; - textArea->obj.area.width = container->obj.area.width; - textArea->obj.area.height = nbgl_getTextHeightInWidth( - textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); - fullHeight += textArea->obj.area.height + textArea->obj.alignmentMarginY; - container->children[container->nbChildren] = (nbgl_obj_t *) textArea; - container->nbChildren++; - } - if (subText != NULL) { - subTextArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); - subTextArea->textColor = BLACK; - subTextArea->text = PIC(subText); - subTextArea->fontId = SMALL_REGULAR_FONT; - subTextArea->style = NO_STYLE; - subTextArea->wrapping = true; - subTextArea->obj.area.width = container->obj.area.width; - subTextArea->obj.area.height = nbgl_getTextHeightInWidth(subTextArea->fontId, - subTextArea->text, - subTextArea->obj.area.width, - subTextArea->wrapping); - subTextArea->textAlignment = MID_LEFT; - subTextArea->obj.alignment = NO_ALIGNMENT; - if (text != NULL) { - subTextArea->obj.alignmentMarginY = TEXT_SUBTEXT_MARGIN; - fullHeight += POST_SUBTEXT_MARGIN; // under the subText - } - else { -#ifdef TARGET_STAX - subTextArea->obj.alignmentMarginY = BORDER_MARGIN; - fullHeight += BORDER_MARGIN; -#else // TARGET_STAX - subTextArea->obj.alignmentMarginY = 26; - fullHeight += 26; // under the subText -#endif // TARGET_STAX - } - container->children[container->nbChildren] = (nbgl_obj_t *) subTextArea; - container->nbChildren++; - fullHeight += subTextArea->obj.area.height + subTextArea->obj.alignmentMarginY; - } - else { - fullHeight += PRE_TEXT_MARGIN; - } - container->obj.area.height = fullHeight; - container->layout = VERTICAL; - container->obj.alignmentMarginX = BORDER_MARGIN; - container->obj.alignment = NO_ALIGNMENT; - // set this new obj as child of main container - layoutAddObject(layoutInt, (nbgl_obj_t *) container); + return addText(layout, text, subText, 0, 0, false); +} - return container->obj.area.height; +/** + * @brief Creates an area with given text (in bold) and sub text (in regular), with a + * > icon on right of text to activate an action when touched, with the given token + * + * @param layout the current layout + * @param text main text (in small bold font), optional + * @param subText description under main text (in small regular font), optional + * @param token token to use in callback when > icon is touched + * @param index index to use in callback when > icon is touched + * @return height of the control if OK + */ +int nbgl_layoutAddTextWithAlias(nbgl_layout_t *layout, + const char *text, + const char *subText, + uint8_t token, + uint8_t index) +{ + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddTextWithAlias():\n"); + return addText(layout, text, subText, token, index, true); } /** diff --git a/lib_nbgl/src/nbgl_page.c b/lib_nbgl/src/nbgl_page.c index 25f0a2a05..f0e037f8b 100644 --- a/lib_nbgl/src/nbgl_page.c +++ b/lib_nbgl/src/nbgl_page.c @@ -199,8 +199,22 @@ static void addContent(nbgl_pageContent_t *content, case INFOS_LIST: { uint8_t i; for (i = 0; i < content->infosList.nbInfos; i++) { - availableHeight -= nbgl_layoutAddText( - layout, content->infosList.infoTypes[i], content->infosList.infoContents[i]); + // if the extension is valid for this index, use a Text with Alias + if ((content->infosList.withExtensions == true) + && (content->infosList.infoExtensions != NULL) + && (content->infosList.infoExtensions[i].fullValue != NULL)) { + availableHeight + -= nbgl_layoutAddTextWithAlias(layout, + content->infosList.infoTypes[i], + content->infosList.infoContents[i], + content->infosList.token, + i); + } + else { + availableHeight -= nbgl_layoutAddText(layout, + content->infosList.infoTypes[i], + content->infosList.infoContents[i]); + } // do not draw a separation line if too low in the container if (availableHeight > 10) { nbgl_layoutAddSeparationLine(layout); diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index dada714fa..f3ef77fd5 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -68,6 +68,7 @@ enum { CONFIRM_TOKEN, REJECT_TOKEN, VALUE_ALIAS_TOKEN, + INFO_ALIAS_TOKEN, BLIND_WARNING_TOKEN, TIP_BOX_TOKEN }; @@ -251,7 +252,9 @@ static char reducedAddress[QRCODE_REDUCED_ADDR_LEN]; **********************/ static void displayReviewPage(uint8_t page, bool forceFullRefresh); static void displayDetailsPage(uint8_t page, bool forceFullRefresh); -static void displayFullValuePage(const nbgl_contentTagValue_t *pair); +static void displayFullValuePage(const char *backText, + const char *aliasText, + const nbgl_contentValueExt_t *extension); static void displayBlindWarning(void); static void displayTipBoxModal(void); static void displaySettingsPage(uint8_t page, bool forceFullRefresh); @@ -444,6 +447,14 @@ static const char *getRejectReviewText(nbgl_operationType_t operationType) static void pageModalCallback(int token, uint8_t index) { LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index); + + if (token == INFO_ALIAS_TOKEN) { + // the icon next to info type alias has been touched + displayFullValuePage(tipBoxInfoList.infoTypes[index], + tipBoxInfoList.infoContents[index], + &tipBoxInfoList.infoExtensions[index]); + return; + } nbgl_pageRelease(modalPageContext); modalPageContext = NULL; if (token == NAV_TOKEN) { @@ -580,7 +591,7 @@ static void pageCallback(int token, uint8_t index) else { pair = genericContext.currentCallback(genericContext.currentElementIdx + index); } - displayFullValuePage(pair); + displayFullValuePage(pair->item, pair->value, pair->extension); } else if (token == BLIND_WARNING_TOKEN) { displayBlindWarning(); @@ -1135,7 +1146,9 @@ static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh) } // function used to display the content of a full value, when touching an alias of a tag/value pair -static void displayFullValuePage(const nbgl_contentTagValue_t *pair) +static void displayFullValuePage(const char *backText, + const char *aliasText, + const nbgl_contentValueExt_t *extension) { nbgl_layoutDescription_t layoutDescription = {.modal = true, .withLeftBorder = true, @@ -1145,16 +1158,16 @@ static void displayFullValuePage(const nbgl_contentTagValue_t *pair) .separationLine = false, .backAndText.token = 0, .backAndText.tuneId = TUNE_TAP_CASUAL, - .backAndText.text = PIC(pair->item)}; + .backAndText.text = PIC(backText)}; genericContext.modalLayout = nbgl_layoutGet(&layoutDescription); // add header with the tag part of the pair, to go back nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc); // add either QR Code or full value text - if (pair->extension->aliasType == QR_CODE_ALIAS) { + if (extension->aliasType == QR_CODE_ALIAS) { #ifdef NBGL_QRCODE - nbgl_layoutQRCode_t qrCode = {.url = pair->extension->fullValue, - .text1 = pair->extension->fullValue, - .text2 = pair->extension->explanation, + nbgl_layoutQRCode_t qrCode = {.url = extension->fullValue, + .text1 = extension->fullValue, + .text2 = extension->explanation, .centered = true, .offsetY = 0}; @@ -1164,17 +1177,17 @@ static void displayFullValuePage(const nbgl_contentTagValue_t *pair) else { const char *info; // add full value text - if (pair->extension->aliasType == ENS_ALIAS) { + if (extension->aliasType == ENS_ALIAS) { info = "ENS names are resolved by Ledger backend."; } - else if (pair->extension->aliasType == ADDRESS_BOOK_ALIAS) { + else if (extension->aliasType == ADDRESS_BOOK_ALIAS) { info = "This account label comes from your Address Book in Ledger Live."; } else { - info = pair->extension->explanation; + info = extension->explanation; } nbgl_layoutAddTextContent( - genericContext.modalLayout, pair->value, pair->extension->fullValue, info); + genericContext.modalLayout, aliasText, extension->fullValue, info); } // draw & refresh nbgl_layoutDraw(genericContext.modalLayout); @@ -1225,13 +1238,16 @@ static void displayTipBoxModal(void) .navWithButtons.quitText = NULL, .progressIndicator = false, .tuneId = TUNE_TAP_CASUAL}; - nbgl_pageContent_t content = {.type = INFOS_LIST, - .topRightIcon = NULL, - .infosList.nbInfos = tipBoxInfoList.nbInfos, - .infosList.infoTypes = tipBoxInfoList.infoTypes, - .infosList.infoContents = tipBoxInfoList.infoContents, - .title = tipBoxModalTitle, - .titleToken = QUIT_TOKEN}; + nbgl_pageContent_t content = {.type = INFOS_LIST, + .topRightIcon = NULL, + .infosList.nbInfos = tipBoxInfoList.nbInfos, + .infosList.withExtensions = tipBoxInfoList.withExtensions, + .infosList.infoTypes = tipBoxInfoList.infoTypes, + .infosList.infoContents = tipBoxInfoList.infoContents, + .infosList.infoExtensions = tipBoxInfoList.infoExtensions, + .infosList.token = INFO_ALIAS_TOKEN, + .title = tipBoxModalTitle, + .titleToken = QUIT_TOKEN}; if (modalPageContext != NULL) { nbgl_pageRelease(modalPageContext); @@ -1788,9 +1804,11 @@ static void useCaseReview(nbgl_operationType_t operationType, tipBoxModalTitle = tipBox->modalTitle; // the only supported type yet is @ref INFOS_LIST if (tipBox->type == INFOS_LIST) { - tipBoxInfoList.nbInfos = tipBox->infos.nbInfos; - tipBoxInfoList.infoTypes = PIC(tipBox->infos.infoTypes); - tipBoxInfoList.infoContents = PIC(tipBox->infos.infoContents); + tipBoxInfoList.nbInfos = tipBox->infos.nbInfos; + tipBoxInfoList.withExtensions = tipBox->infos.withExtensions; + tipBoxInfoList.infoTypes = PIC(tipBox->infos.infoTypes); + tipBoxInfoList.infoContents = PIC(tipBox->infos.infoContents); + tipBoxInfoList.infoExtensions = PIC(tipBox->infos.infoExtensions); } }