From d41353e7f1b5aff9a1aca8d11ab0202db616716b Mon Sep 17 00:00:00 2001 From: Nicolas Roggeman Date: Tue, 16 Jul 2024 10:10:18 +0200 Subject: [PATCH 1/2] Create up-footer layout object to contain some types of objects --- lib_nbgl/include/nbgl_content.h | 13 + lib_nbgl/include/nbgl_layout.h | 32 ++ lib_nbgl/include/nbgl_obj.h | 1 + lib_nbgl/src/nbgl_layout.c | 502 ++++++++++++++++++---------- lib_nbgl/src/nbgl_layout_internal.h | 18 +- 5 files changed, 382 insertions(+), 184 deletions(-) diff --git a/lib_nbgl/include/nbgl_content.h b/lib_nbgl/include/nbgl_content.h index c9a502e2b..324d60143 100644 --- a/lib_nbgl/include/nbgl_content.h +++ b/lib_nbgl/include/nbgl_content.h @@ -301,6 +301,19 @@ typedef struct { #endif // HAVE_PIEZO_SOUND } nbgl_contentBarsList_t; +/** + * @brief This structure contains data to build a tip-box, on top of a footer, + * on bottom of a content center + */ +typedef struct { + const char *text; ///< text of the tip-box + const nbgl_icon_details_t *icon; ///< icon of the tip-box + uint8_t token; ///< token used when tip-box is tapped +#ifdef HAVE_PIEZO_SOUND + tune_index_e tuneId; ///< tune played when tip-box is tapped +#endif // HAVE_PIEZO_SOUND +} nbgl_contentTipBox_t; + /** * @brief The different types of predefined contents * diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h index 096b41671..51d2f9ce5 100644 --- a/lib_nbgl/include/nbgl_layout.h +++ b/lib_nbgl/include/nbgl_layout.h @@ -494,6 +494,37 @@ typedef struct { nbgl_layoutChoiceButtons_t choiceButtons; ///< if type is @ref FOOTER_SIMPLE_BUTTON }; } nbgl_layoutFooter_t; + +/** + * @brief The different types of area on top of footer + * + */ +typedef enum { + UP_FOOTER_LONG_PRESS = 0, ///< long-press button + UP_FOOTER_BUTTON, ///< simple button + UP_FOOTER_HORIZONTAL_BUTTONS, ///< 2 buttons, on the same line + UP_FOOTER_TIP_BOX, ///< Tip-box + NB_UP_FOOTER_TYPES +} nbgl_layoutUpFooterType_t; + +/** + * @brief This structure contains info to build an up-footer (area on top of footer). + * + */ +typedef struct { + nbgl_layoutUpFooterType_t type; ///< type of up-footer + union { + struct { + const char *text; ///< text in the long-press button + uint8_t token; ///< token used when button is long-pressed + tune_index_e tuneId; ///< tune played when button is long-pressed + } longPress; ///< if type is @ref UP_FOOTER_LONG_PRESS + nbgl_layoutButton_t button; ///< if type is @ref UP_FOOTER_BUTTON + nbgl_layoutHorizontalButtons_t + horizontalButtons; ///< if type is @ref UP_FOOTER_HORIZONTAL_BUTTONS + nbgl_contentTipBox_t tipBox; ///< if type is @ref UP_FOOTER_TIP_BOX + }; +} nbgl_layoutUpFooter_t; #endif // HAVE_SE_TOUCH /** @@ -574,6 +605,7 @@ int nbgl_layoutAddSplitFooter(nbgl_layout_t *layout, tune_index_e tuneId); int nbgl_layoutAddHeader(nbgl_layout_t *layout, const nbgl_layoutHeader_t *headerDesc); int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_t *footerDesc); +int nbgl_layoutAddUpFooter(nbgl_layout_t *layout, const nbgl_layoutUpFooter_t *upFooterDesc); int nbgl_layoutAddNavigationBar(nbgl_layout_t *layout, const nbgl_layoutNavigationBar_t *info); int nbgl_layoutAddBottomButton(nbgl_layout_t *layout, const nbgl_icon_details_t *icon, diff --git a/lib_nbgl/include/nbgl_obj.h b/lib_nbgl/include/nbgl_obj.h index 134233eae..cd3d13626 100644 --- a/lib_nbgl/include/nbgl_obj.h +++ b/lib_nbgl/include/nbgl_obj.h @@ -560,6 +560,7 @@ enum { VALUE_BUTTON_2_ID, VALUE_BUTTON_3_ID, LONG_PRESS_BUTTON_ID, + TIP_BOX_ID, CONTROLS_ID, // when multiple controls in the same pages (buttons, switches, radios) NB_CONTROL_IDS }; diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c index a167d0b9c..9375b8b00 100644 --- a/lib_nbgl/src/nbgl_layout.c +++ b/lib_nbgl/src/nbgl_layout.c @@ -40,15 +40,19 @@ #define TAG_VALUE_ICON_WIDTH 32 #ifdef TARGET_STAX -#define RADIO_CHOICE_HEIGHT 96 -#define FOOTER_HEIGHT 80 -#define BAR_INTERVALE 12 -#define BACK_KEY_WIDTH 88 +#define RADIO_CHOICE_HEIGHT 96 +#define FOOTER_HEIGHT 80 +#define BAR_INTERVALE 12 +#define BACK_KEY_WIDTH 88 +#define FOOTER_BUTTON_HEIGHT 128 +#define UP_FOOTER_BUTTON_HEIGHT 120 #else // TARGET_STAX -#define RADIO_CHOICE_HEIGHT 92 -#define FOOTER_HEIGHT 80 -#define BAR_INTERVALE 16 -#define BACK_KEY_WIDTH 104 +#define RADIO_CHOICE_HEIGHT 92 +#define FOOTER_HEIGHT 80 +#define BAR_INTERVALE 16 +#define BACK_KEY_WIDTH 104 +#define FOOTER_BUTTON_HEIGHT 136 +#define UP_FOOTER_BUTTON_HEIGHT 136 #endif // TARGET_STAX // refresh period of the spinner, in ms @@ -1794,73 +1798,15 @@ int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceBu int nbgl_layoutAddHorizontalButtons(nbgl_layout_t *layout, const nbgl_layoutHorizontalButtons_t *info) { - layoutObj_t *obj; - nbgl_button_t *button; - nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_layoutUpFooter_t upFooterDesc = {.type = UP_FOOTER_HORIZONTAL_BUTTONS, + .horizontalButtons.leftIcon = info->leftIcon, + .horizontalButtons.leftToken = info->leftToken, + .horizontalButtons.rightText = info->rightText, + .horizontalButtons.rightToken = info->rightToken, + .horizontalButtons.tuneId = info->tuneId}; LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddHorizontalButtons():\n"); - if (layout == NULL) { - return -1; - } - - // icon & text cannot be NULL - if ((info->leftIcon == NULL) || (info->rightText == NULL)) { - return -1; - } - - // create left button (in white) at first - button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); - obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) button, info->leftToken, info->tuneId); - if (obj == NULL) { - return -1; - } - // associate with with index 1 - obj->index = 1; - button->obj.alignment = BOTTOM_LEFT; - button->obj.alignmentMarginX = BORDER_MARGIN; -#ifdef TARGET_STAX - button->obj.alignmentMarginY = 20; // 20 pixels from screen bottom -#else // TARGET_STAX - button->obj.alignmentMarginY = 24; // 24 pixels from screen bottom -#endif // TARGET_STAX - button->borderColor = LIGHT_GRAY; - button->innerColor = WHITE; - button->foregroundColor = BLACK; - button->obj.area.width = BUTTON_DIAMETER; - button->obj.area.height = BUTTON_DIAMETER; - button->radius = BUTTON_RADIUS; - button->icon = PIC(info->leftIcon); - button->fontId = SMALL_BOLD_FONT; - button->obj.touchMask = (1 << TOUCHED); - button->obj.touchId = CHOICE_2_ID; - // set this new button as child of the container - layoutAddObject(layoutInt, (nbgl_obj_t *) button); - - // then black button, on right - button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); - obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) button, info->rightToken, info->tuneId); - if (obj == NULL) { - return -1; - } - // associate with with index 0 - obj->index = 0; - button->obj.alignment = BOTTOM_RIGHT; - button->obj.alignmentMarginX = BORDER_MARGIN; - button->obj.alignmentMarginY = 24; // 24 pixels from screen bottom - button->innerColor = BLACK; - button->borderColor = BLACK; - button->foregroundColor = WHITE; - button->obj.area.width = AVAILABLE_WIDTH - BUTTON_DIAMETER - 16; - button->obj.area.height = BUTTON_DIAMETER; - button->radius = BUTTON_RADIUS; - button->text = PIC(info->rightText); - button->fontId = SMALL_BOLD_FONT; - button->obj.touchMask = (1 << TOUCHED); - button->obj.touchId = CHOICE_1_ID; - // set this new button as child of the container - layoutAddObject(layoutInt, (nbgl_obj_t *) button); - - return 0; + return nbgl_layoutAddUpFooter(layout, &upFooterDesc); } /** @@ -2125,17 +2071,28 @@ int nbgl_layoutAddButton(nbgl_layout_t *layout, const nbgl_layoutButton_t *butto } // Add in footer if matching - if ((buttonInfo->onBottom) && (!buttonInfo->fittingContent) - && (layoutInt->footerContainer == NULL)) { - nbgl_layoutFooter_t footerDesc; - footerDesc.type = FOOTER_SIMPLE_BUTTON; - footerDesc.separationLine = false; - footerDesc.button.text = buttonInfo->text; - footerDesc.button.token = buttonInfo->token; - footerDesc.button.tuneId = buttonInfo->tuneId; - footerDesc.button.icon = buttonInfo->icon; - footerDesc.button.style = buttonInfo->style; - return nbgl_layoutAddExtendedFooter(layout, &footerDesc); + if ((buttonInfo->onBottom) && (!buttonInfo->fittingContent)) { + if (layoutInt->footerContainer == NULL) { + nbgl_layoutFooter_t footerDesc; + footerDesc.type = FOOTER_SIMPLE_BUTTON; + footerDesc.separationLine = false; + footerDesc.button.text = buttonInfo->text; + footerDesc.button.token = buttonInfo->token; + footerDesc.button.tuneId = buttonInfo->tuneId; + footerDesc.button.icon = buttonInfo->icon; + footerDesc.button.style = buttonInfo->style; + return nbgl_layoutAddExtendedFooter(layout, &footerDesc); + } + else { + nbgl_layoutUpFooter_t upFooterDesc; + upFooterDesc.type = UP_FOOTER_BUTTON; + upFooterDesc.button.text = buttonInfo->text; + upFooterDesc.button.token = buttonInfo->token; + upFooterDesc.button.tuneId = buttonInfo->tuneId; + upFooterDesc.button.icon = buttonInfo->icon; + upFooterDesc.button.style = buttonInfo->style; + return nbgl_layoutAddUpFooter(layout, &upFooterDesc); + } } button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); @@ -2145,19 +2102,9 @@ int nbgl_layoutAddButton(nbgl_layout_t *layout, const nbgl_layoutButton_t *butto return -1; } - if (buttonInfo->onBottom != true) { - button->obj.alignmentMarginX = BORDER_MARGIN; - button->obj.alignmentMarginY = 12; - button->obj.alignment = NO_ALIGNMENT; - } - else { - button->obj.alignment = BOTTOM_MIDDLE; -#ifdef TARGET_STAX - button->obj.alignmentMarginY = 20; -#else // TARGET_STAX - button->obj.alignmentMarginY = 24; -#endif // TARGET_STAX - } + button->obj.alignmentMarginX = BORDER_MARGIN; + button->obj.alignmentMarginY = 12; + button->obj.alignment = NO_ALIGNMENT; if (buttonInfo->style == BLACK_BACKGROUND) { button->innerColor = BLACK; button->foregroundColor = WHITE; @@ -2219,81 +2166,17 @@ int nbgl_layoutAddLongPressButton(nbgl_layout_t *layout, uint8_t token, tune_index_e tuneId) { - layoutObj_t *obj; - nbgl_button_t *button; - nbgl_text_area_t *textArea; - nbgl_progress_bar_t *progressBar; - nbgl_container_t *container; - nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; - nbgl_line_t *line; + nbgl_layoutUpFooter_t upFooterDesc = {.type = UP_FOOTER_LONG_PRESS, + .longPress.text = text, + .longPress.token = token, + .longPress.tuneId = tuneId}; LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddLongPressButton():\n"); if (layout == NULL) { return -1; } - container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); - obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) container, token, tuneId); - if (obj == NULL) { - return -1; - } - - container->obj.area.width = SCREEN_WIDTH; - container->obj.area.height = LONG_PRESS_BUTTON_HEIGHT; - container->layout = VERTICAL; - container->nbChildren = 4; // progress-bar + text + line + button - container->children - = (nbgl_obj_t **) nbgl_containerPoolGet(container->nbChildren, layoutInt->layer); - container->obj.alignment = BOTTOM_MIDDLE; - container->obj.touchId = LONG_PRESS_BUTTON_ID; - container->obj.touchMask = ((1 << TOUCHING) | (1 << TOUCH_RELEASED) | (1 << OUT_OF_TOUCH) - | (1 << SWIPED_LEFT) | (1 << SWIPED_RIGHT)); - - button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); - button->obj.alignmentMarginX = BORDER_MARGIN; - button->obj.alignment = MID_RIGHT; - button->innerColor = BLACK; - button->foregroundColor = WHITE; - button->borderColor = BLACK; - button->obj.area.width = BUTTON_DIAMETER; - button->obj.area.height = BUTTON_DIAMETER; - button->radius = BUTTON_RADIUS; - button->icon = PIC(&VALIDATE_ICON); - container->children[0] = (nbgl_obj_t *) button; - - textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); - textArea->textColor = BLACK; - textArea->text = PIC(text); - textArea->textAlignment = MID_LEFT; - textArea->fontId = LARGE_MEDIUM_FONT; - textArea->wrapping = true; - textArea->obj.area.width - = container->obj.area.width - 3 * BORDER_MARGIN - button->obj.area.width; - textArea->obj.area.height = nbgl_getTextHeightInWidth( - textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); - textArea->style = NO_STYLE; - textArea->obj.alignment = MID_LEFT; - textArea->obj.alignmentMarginX = BORDER_MARGIN; - container->children[1] = (nbgl_obj_t *) textArea; - - line = createHorizontalLine(layoutInt->layer); - line->offset = 3; - line->obj.alignment = TOP_MIDDLE; - container->children[2] = (nbgl_obj_t *) line; - - progressBar = (nbgl_progress_bar_t *) nbgl_objPoolGet(PROGRESS_BAR, layoutInt->layer); - progressBar->withBorder = false; - progressBar->obj.area.width = container->obj.area.width; - progressBar->obj.area.height = 8; - progressBar->obj.alignment = TOP_MIDDLE; - progressBar->obj.alignmentMarginY = 4; - progressBar->obj.alignTo = NULL; - container->children[3] = (nbgl_obj_t *) progressBar; - - // set this new container as child of the main container - layoutAddObject(layoutInt, (nbgl_obj_t *) container); - - return container->obj.area.height; + return nbgl_layoutAddUpFooter(layout, &upFooterDesc); } /** @@ -2754,7 +2637,7 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ #ifdef TARGET_STAX textArea->obj.area.width = 160; #else // TARGET_STAX - textArea->obj.area.width = 192; + textArea->obj.area.width = 192; #endif // TARGET_STAX textArea->obj.area.height = SIMPLE_FOOTER_HEIGHT; textArea->text = PIC(footerDesc->textAndNav.text); @@ -2855,16 +2738,12 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ button->borderColor = LIGHT_GRAY; } } - button->text = PIC(footerDesc->button.text); - button->fontId = SMALL_BOLD_FONT; - button->icon = PIC(footerDesc->button.icon); - button->radius = BUTTON_RADIUS; - button->obj.area.height = BUTTON_DIAMETER; -#ifdef TARGET_STAX - layoutInt->footerContainer->obj.area.height = 128; -#else // TARGET_STAX - layoutInt->footerContainer->obj.area.height = 136; -#endif // TARGET_STAX + button->text = PIC(footerDesc->button.text); + button->fontId = SMALL_BOLD_FONT; + button->icon = PIC(footerDesc->button.icon); + button->radius = BUTTON_RADIUS; + button->obj.area.height = BUTTON_DIAMETER; + layoutInt->footerContainer->obj.area.height = FOOTER_BUTTON_HEIGHT; if (footerDesc->button.text == NULL) { button->obj.area.width = BUTTON_DIAMETER; } @@ -3024,6 +2903,275 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ return layoutInt->footerContainer->obj.area.height; } +/** + * @brief Creates a touchable area on top of the footer of the screen, containing various controls, + * described in the given structure. This up-footer is not part of the main container + * + * @param layout the current layout + * @param upFooterDesc description of the up-footer + * @return height of the control if OK + */ +int nbgl_layoutAddUpFooter(nbgl_layout_t *layout, const nbgl_layoutUpFooter_t *upFooterDesc) +{ + nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + layoutObj_t *obj; + nbgl_text_area_t *textArea; + nbgl_line_t *line; + nbgl_button_t *button; + + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddUpFooter():\n"); + if (layout == NULL) { + return -1; + } + if ((upFooterDesc == NULL) || (upFooterDesc->type >= NB_UP_FOOTER_TYPES)) { + return -2; + } + + layoutInt->upFooterContainer + = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + layoutInt->upFooterContainer->obj.area.width = SCREEN_WIDTH; + layoutInt->upFooterContainer->layout = VERTICAL; + // maximum 4 children for long press button + layoutInt->upFooterContainer->children + = (nbgl_obj_t **) nbgl_containerPoolGet(4, layoutInt->layer); + layoutInt->upFooterContainer->obj.alignTo = (nbgl_obj_t *) layoutInt->container; + layoutInt->upFooterContainer->obj.alignment = BOTTOM_MIDDLE; + + switch (upFooterDesc->type) { + case UP_FOOTER_LONG_PRESS: { + nbgl_progress_bar_t *progressBar; + + obj = layoutAddCallbackObj(layoutInt, + (nbgl_obj_t *) layoutInt->upFooterContainer, + upFooterDesc->longPress.token, + upFooterDesc->longPress.tuneId); + if (obj == NULL) { + return -1; + } + layoutInt->upFooterContainer->nbChildren = 4; + layoutInt->upFooterContainer->obj.area.height = LONG_PRESS_BUTTON_HEIGHT; + layoutInt->upFooterContainer->obj.touchId = LONG_PRESS_BUTTON_ID; + layoutInt->upFooterContainer->obj.touchMask + = ((1 << TOUCHING) | (1 << TOUCH_RELEASED) | (1 << OUT_OF_TOUCH) + | (1 << SWIPED_LEFT) | (1 << SWIPED_RIGHT)); + + button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); + button->obj.alignmentMarginX = BORDER_MARGIN; + button->obj.alignment = MID_RIGHT; + button->innerColor = BLACK; + button->foregroundColor = WHITE; + button->borderColor = BLACK; + button->obj.area.width = BUTTON_DIAMETER; + button->obj.area.height = BUTTON_DIAMETER; + button->radius = BUTTON_RADIUS; + button->icon = PIC(&VALIDATE_ICON); + layoutInt->upFooterContainer->children[0] = (nbgl_obj_t *) button; + + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = BLACK; + textArea->text = PIC(upFooterDesc->longPress.text); + textArea->textAlignment = MID_LEFT; + textArea->fontId = LARGE_MEDIUM_FONT; + textArea->wrapping = true; + textArea->obj.area.width = SCREEN_WIDTH - 3 * BORDER_MARGIN - button->obj.area.width; + textArea->obj.area.height = nbgl_getTextHeightInWidth( + textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); + textArea->style = NO_STYLE; + textArea->obj.alignment = MID_LEFT; + textArea->obj.alignmentMarginX = BORDER_MARGIN; + layoutInt->upFooterContainer->children[1] = (nbgl_obj_t *) textArea; + + line = createHorizontalLine(layoutInt->layer); + line->offset = 3; + line->obj.alignment = TOP_MIDDLE; + layoutInt->upFooterContainer->children[2] = (nbgl_obj_t *) line; + + progressBar = (nbgl_progress_bar_t *) nbgl_objPoolGet(PROGRESS_BAR, layoutInt->layer); + progressBar->withBorder = false; + progressBar->obj.area.width = SCREEN_WIDTH; + progressBar->obj.area.height = 8; + progressBar->obj.alignment = TOP_MIDDLE; + progressBar->obj.alignmentMarginY = 4; + progressBar->obj.alignTo = NULL; + layoutInt->upFooterContainer->children[3] = (nbgl_obj_t *) progressBar; + break; + } + case UP_FOOTER_BUTTON: { + button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); + obj = layoutAddCallbackObj(layoutInt, + (nbgl_obj_t *) button, + upFooterDesc->button.token, + upFooterDesc->button.tuneId); + if (obj == NULL) { + return -1; + } + + layoutInt->upFooterContainer->nbChildren = 1; + layoutInt->upFooterContainer->obj.area.height = UP_FOOTER_BUTTON_HEIGHT; + button->obj.alignment = CENTER; + + if (upFooterDesc->button.style == BLACK_BACKGROUND) { + button->innerColor = BLACK; + button->foregroundColor = WHITE; + } + else { + button->innerColor = WHITE; + button->foregroundColor = BLACK; + } + if (upFooterDesc->button.style == NO_BORDER) { + button->borderColor = WHITE; + } + else { + if (upFooterDesc->button.style == BLACK_BACKGROUND) { + button->borderColor = BLACK; + } + else { + button->borderColor = LIGHT_GRAY; + } + } + button->text = PIC(upFooterDesc->button.text); + button->fontId = SMALL_BOLD_FONT; + button->icon = PIC(upFooterDesc->button.icon); + button->obj.area.width = AVAILABLE_WIDTH; + button->obj.area.height = BUTTON_DIAMETER; + button->radius = BUTTON_RADIUS; + + button->obj.alignTo = NULL; + button->obj.touchMask = (1 << TOUCHED); + button->obj.touchId = SINGLE_BUTTON_ID; + layoutInt->upFooterContainer->children[0] = (nbgl_obj_t *) button; + break; + } + case UP_FOOTER_HORIZONTAL_BUTTONS: { + // icon & text cannot be NULL + if ((upFooterDesc->horizontalButtons.leftIcon == NULL) + || (upFooterDesc->horizontalButtons.rightText == NULL)) { + return -1; + } + + layoutInt->upFooterContainer->nbChildren = 2; + layoutInt->upFooterContainer->obj.area.height = UP_FOOTER_BUTTON_HEIGHT; + + // create left button (in white) at first + button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); + obj = layoutAddCallbackObj(layoutInt, + (nbgl_obj_t *) button, + upFooterDesc->horizontalButtons.leftToken, + upFooterDesc->horizontalButtons.tuneId); + if (obj == NULL) { + return -1; + } + // associate with with index 1 + obj->index = 1; + button->obj.alignment = MID_LEFT; + button->obj.alignmentMarginX = BORDER_MARGIN; + button->borderColor = LIGHT_GRAY; + button->innerColor = WHITE; + button->foregroundColor = BLACK; + button->obj.area.width = BUTTON_DIAMETER; + button->obj.area.height = BUTTON_DIAMETER; + button->radius = BUTTON_RADIUS; + button->icon = PIC(upFooterDesc->horizontalButtons.leftIcon); + button->fontId = SMALL_BOLD_FONT; + button->obj.touchMask = (1 << TOUCHED); + button->obj.touchId = CHOICE_2_ID; + layoutInt->upFooterContainer->children[0] = (nbgl_obj_t *) button; + + // then black button, on right + button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); + obj = layoutAddCallbackObj(layoutInt, + (nbgl_obj_t *) button, + upFooterDesc->horizontalButtons.rightToken, + upFooterDesc->horizontalButtons.tuneId); + if (obj == NULL) { + return -1; + } + // associate with with index 0 + obj->index = 0; + button->obj.alignment = MID_RIGHT; + button->obj.alignmentMarginX = BORDER_MARGIN; + button->innerColor = BLACK; + button->borderColor = BLACK; + button->foregroundColor = WHITE; + button->obj.area.width = AVAILABLE_WIDTH - BUTTON_DIAMETER - 16; + button->obj.area.height = BUTTON_DIAMETER; + button->radius = BUTTON_RADIUS; + button->text = PIC(upFooterDesc->horizontalButtons.rightText); + button->fontId = SMALL_BOLD_FONT; + button->obj.touchMask = (1 << TOUCHED); + button->obj.touchId = CHOICE_1_ID; + layoutInt->upFooterContainer->children[1] = (nbgl_obj_t *) button; + break; + } + case UP_FOOTER_TIP_BOX: { + // text cannot be NULL + if (upFooterDesc->tipBox.text == NULL) { + return -1; + } + obj = layoutAddCallbackObj(layoutInt, + (nbgl_obj_t *) layoutInt->upFooterContainer, + upFooterDesc->tipBox.token, + upFooterDesc->tipBox.tuneId); + if (obj == NULL) { + return -1; + } + layoutInt->upFooterContainer->nbChildren = 3; + layoutInt->upFooterContainer->obj.touchId = TIP_BOX_ID; + layoutInt->upFooterContainer->obj.touchMask = (1 << TOUCHED); + + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = BLACK; + textArea->text = PIC(upFooterDesc->tipBox.text); + textArea->textAlignment = MID_LEFT; + textArea->fontId = SMALL_REGULAR_FONT; + textArea->wrapping = true; + textArea->obj.area.width = AVAILABLE_WIDTH; + if (upFooterDesc->tipBox.icon != NULL) { + textArea->obj.area.width + -= ((nbgl_icon_details_t *) PIC(upFooterDesc->tipBox.icon))->width + + BORDER_MARGIN; + } + textArea->obj.area.height = nbgl_getTextHeightInWidth( + textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); + textArea->obj.alignment = MID_LEFT; + textArea->obj.alignmentMarginX = BORDER_MARGIN; + layoutInt->upFooterContainer->children[0] = (nbgl_obj_t *) textArea; + layoutInt->upFooterContainer->obj.area.height = textArea->obj.area.height; + + line = createHorizontalLine(layoutInt->layer); + line->offset = 3; + line->obj.alignment = TOP_MIDDLE; + layoutInt->upFooterContainer->children[1] = (nbgl_obj_t *) line; + + if (upFooterDesc->tipBox.icon != NULL) { + nbgl_image_t *image = (nbgl_image_t *) nbgl_objPoolGet(IMAGE, layoutInt->layer); + image->obj.alignmentMarginX = BORDER_MARGIN; + image->obj.alignment = MID_RIGHT; + image->foregroundColor = BLACK; + image->buffer = PIC(upFooterDesc->tipBox.icon); + layoutInt->upFooterContainer->children[2] = (nbgl_obj_t *) image; + if (layoutInt->upFooterContainer->obj.area.height < image->buffer->height) { + layoutInt->upFooterContainer->obj.area.height = image->buffer->height; + } + } + layoutInt->upFooterContainer->obj.area.height += 2 * BOTTOM_BORDER_MARGIN; + + break; + } + default: + return -2; + } + + // subtract up footer height from main container height + layoutInt->container->obj.area.height -= layoutInt->upFooterContainer->obj.area.height; + + layoutInt->children[UP_FOOTER_INDEX] = (nbgl_obj_t *) layoutInt->upFooterContainer; + + layoutInt->upFooterType = upFooterDesc->type; + + return layoutInt->upFooterContainer->obj.area.height; +} + /** * @deprecated * @brief Creates a kind of navigation bar with an optional <- arrow on the left. This widget is diff --git a/lib_nbgl/src/nbgl_layout_internal.h b/lib_nbgl/src/nbgl_layout_internal.h index 40602361e..a20b5988b 100644 --- a/lib_nbgl/src/nbgl_layout_internal.h +++ b/lib_nbgl/src/nbgl_layout_internal.h @@ -57,6 +57,7 @@ enum { TOP_RIGHT_BUTTON_INDEX, MAIN_CONTAINER_INDEX, FOOTER_INDEX, + UP_FOOTER_INDEX, LEFT_BORDER_INDEX, NB_MAX_SCREEN_CHILDREN }; @@ -75,14 +76,17 @@ typedef struct nbgl_layoutInternal_s { uint8_t nbChildren; ///< number of children in above array nbgl_obj_t **children; ///< children for main screen - uint8_t nbPages; ///< number of pages for navigation bar - uint8_t activePage; ///< index of active page for navigation bar - nbgl_layoutHeaderType_t headerType; ///< type of header - nbgl_layoutFooterType_t footerType; ///< type of footer + uint8_t nbPages; ///< number of pages for navigation bar + uint8_t activePage; ///< index of active page for navigation bar + nbgl_layoutHeaderType_t headerType; ///< type of header + nbgl_layoutFooterType_t footerType; ///< type of footer + nbgl_layoutUpFooterType_t upFooterType; ///< type of up-footer nbgl_container_t - *headerContainer; // container used to store header (progress, back, empty space...) - nbgl_container_t *footerContainer; // container used to store footer (buttons, nav....) - nbgl_text_area_t *tapText; + *headerContainer; ///< container used to store header (progress, back, empty space...) + nbgl_container_t *footerContainer; ///< container used to store footer (buttons, nav....) + nbgl_container_t *upFooterContainer; ///< container used on top on footer to store special + ///< contents like tip-box or long-press button + nbgl_text_area_t *tapText; nbgl_layoutTouchCallback_t callback; // user callback for all controls // This is the pool of callback objects, potentially used by this layout layoutObj_t callbackObjPool[LAYOUT_OBJ_POOL_LEN]; From fa3dac6963c08baaec5f603dc0deb1e5bee5012d Mon Sep 17 00:00:00 2001 From: Nicolas Roggeman Date: Tue, 16 Jul 2024 10:11:40 +0200 Subject: [PATCH 2/2] Add extended-center content to contain both centered info and tip-box --- lib_nbgl/include/nbgl_content.h | 10 ++ lib_nbgl/include/nbgl_page.h | 1 + lib_nbgl/include/nbgl_use_case.h | 24 +++++ lib_nbgl/src/nbgl_page.c | 91 +++++++++--------- lib_nbgl/src/nbgl_use_case.c | 152 ++++++++++++++++++++++++++----- 5 files changed, 212 insertions(+), 66 deletions(-) diff --git a/lib_nbgl/include/nbgl_content.h b/lib_nbgl/include/nbgl_content.h index 324d60143..5dcbb47d2 100644 --- a/lib_nbgl/include/nbgl_content.h +++ b/lib_nbgl/include/nbgl_content.h @@ -314,12 +314,21 @@ typedef struct { #endif // HAVE_PIEZO_SOUND } nbgl_contentTipBox_t; +/** + * @brief This structure contains data to build a @ref EXTENDED_CENTER content + */ +typedef struct { + nbgl_contentCenter_t contentCenter; ///< centered content (icon + text(s)) + nbgl_contentTipBox_t tipBox; ///< if text field is NULL, no tip-box +} nbgl_contentExtendedCenter_t; + /** * @brief The different types of predefined contents * */ typedef enum { CENTERED_INFO = 0, ///< a centered info + EXTENDED_CENTER, ///< a centered content and a possible tip-box INFO_LONG_PRESS, ///< a centered info and a long press button INFO_BUTTON, ///< a centered info and a simple black button TAG_VALUE_LIST, ///< list of tag/value pairs @@ -336,6 +345,7 @@ typedef enum { */ typedef union { nbgl_contentCenteredInfo_t centeredInfo; ///< @ref CENTERED_INFO type + nbgl_contentExtendedCenter_t extendedCenter; ///< @ref EXTENDED_CENTER type nbgl_contentInfoLongPress_t infoLongPress; ///< @ref INFO_LONG_PRESS type nbgl_contentInfoButton_t infoButton; ///< @ref INFO_BUTTON type nbgl_contentTagValueList_t tagValueList; ///< @ref TAG_VALUE_LIST type diff --git a/lib_nbgl/include/nbgl_page.h b/lib_nbgl/include/nbgl_page.h index 5a7f51338..5edfbff9e 100644 --- a/lib_nbgl/include/nbgl_page.h +++ b/lib_nbgl/include/nbgl_page.h @@ -61,6 +61,7 @@ typedef struct nbgl_pageContent_s { nbgl_contentType_t type; ///< type of page content in the following union union { nbgl_contentCenteredInfo_t centeredInfo; ///< @ref CENTERED_INFO type + nbgl_contentExtendedCenter_t extendedCenter; ///< @ref EXTENDED_CENTER type nbgl_contentInfoLongPress_t infoLongPress; ///< @ref INFO_LONG_PRESS type nbgl_contentInfoButton_t infoButton; ///< @ref INFO_BUTTON type nbgl_contentTagValueList_t tagValueList; ///< @ref TAG_VALUE_LIST type diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index ccff6adab..c641597c8 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -150,6 +150,21 @@ typedef struct { nbgl_callback_t callback; ///< function to call when action button is touched in Home page } nbgl_homeAction_t; +/** + * @brief The necessary parameters to build a tip-box in first review page and + * the modal if this tip box is touched + * + */ +typedef struct { + const char *text; ///< text of the tip-box + const nbgl_icon_details_t *icon; ///< icon of the tip-box + const char *modalTitle; ///< title given to modal window displayed when tip-box is touched + nbgl_contentType_t type; ///< type of page content in the following union + union { + const nbgl_contentInfoList_t infos; ///< infos pairs displayed in modal. + }; +} nbgl_tipBox_t; + /** * @brief The different types of operation to review * @@ -219,6 +234,15 @@ void nbgl_useCaseReview(nbgl_operationType_t operationType, const char *finishTitle, nbgl_choiceCallback_t choiceCallback); +void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + const nbgl_tipBox_t *tipBox, + nbgl_choiceCallback_t choiceCallback); + void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, diff --git a/lib_nbgl/src/nbgl_page.c b/lib_nbgl/src/nbgl_page.c index 5229a08f7..cf99d9b4c 100644 --- a/lib_nbgl/src/nbgl_page.c +++ b/lib_nbgl/src/nbgl_page.c @@ -55,15 +55,15 @@ static void addContent(nbgl_pageContent_t *content, } switch (content->type) { case INFO_LONG_PRESS: { - nbgl_layoutCenteredInfo_t centeredInfo; - centeredInfo.icon = content->infoLongPress.icon; - centeredInfo.text1 = content->infoLongPress.text; - centeredInfo.text2 = NULL; - centeredInfo.text3 = NULL; - centeredInfo.style = LARGE_CASE_INFO; - centeredInfo.offsetY = -LONG_PRESS_BUTTON_HEIGHT / 2; - centeredInfo.onTop = false; - nbgl_layoutAddCenteredInfo(layout, ¢eredInfo); + nbgl_contentCenter_t centeredInfo; + centeredInfo.icon = content->infoLongPress.icon; + centeredInfo.title = content->infoLongPress.text; + centeredInfo.smallTitle = NULL; + centeredInfo.description = NULL; + centeredInfo.iconHug = 0; + centeredInfo.subText = NULL; + centeredInfo.padding = false; + nbgl_layoutAddContentCenter(layout, ¢eredInfo); nbgl_layoutAddLongPressButton(layout, content->infoLongPress.longPressText, content->infoLongPress.longPressToken, @@ -71,17 +71,17 @@ static void addContent(nbgl_pageContent_t *content, break; } case INFO_BUTTON: { - nbgl_layoutCenteredInfo_t centeredInfo; - nbgl_layoutButton_t buttonInfo; - - centeredInfo.icon = content->infoButton.icon; - centeredInfo.text1 = content->infoButton.text; - centeredInfo.text2 = NULL; - centeredInfo.text3 = NULL; - centeredInfo.style = LARGE_CASE_INFO; - centeredInfo.offsetY = -40; - centeredInfo.onTop = false; - nbgl_layoutAddCenteredInfo(layout, ¢eredInfo); + nbgl_contentCenter_t centeredInfo; + nbgl_layoutButton_t buttonInfo; + + centeredInfo.icon = content->infoButton.icon; + centeredInfo.title = content->infoButton.text; + centeredInfo.smallTitle = NULL; + centeredInfo.description = NULL; + centeredInfo.iconHug = 0; + centeredInfo.subText = NULL; + centeredInfo.padding = false; + nbgl_layoutAddContentCenter(layout, ¢eredInfo); buttonInfo.fittingContent = false; buttonInfo.icon = NULL; @@ -99,6 +99,23 @@ static void addContent(nbgl_pageContent_t *content, } nbgl_layoutAddCenteredInfo(layout, &content->centeredInfo); break; + + case EXTENDED_CENTER: + if ((!headerAdded) && (content->extendedCenter.tipBox.text == NULL)) { + addEmptyHeader(layout, SMALL_CENTERING_HEADER); + } + nbgl_layoutAddContentCenter(layout, &content->extendedCenter.contentCenter); + if (content->extendedCenter.tipBox.text != NULL) { + nbgl_layoutUpFooter_t upFooterDesc + = {.type = UP_FOOTER_TIP_BOX, + .tipBox.text = content->extendedCenter.tipBox.text, + .tipBox.icon = content->extendedCenter.tipBox.icon, + .tipBox.token = content->extendedCenter.tipBox.token, + .tipBox.tuneId = content->extendedCenter.tipBox.tuneId}; + nbgl_layoutAddUpFooter(layout, &upFooterDesc); + } + break; + case TAG_VALUE_LIST: // add a space of 32/40px if no header already added if (!headerAdded) { @@ -420,8 +437,14 @@ nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionC nbgl_page_t *nbgl_pageDrawConfirmation(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageConfirmationDescription_t *info) { - nbgl_layoutDescription_t layoutDescription; - nbgl_layout_t *layout; + nbgl_layoutDescription_t layoutDescription; + nbgl_layout_t *layout; + nbgl_layoutChoiceButtons_t buttonsInfo + = {.bottomText = (info->cancelText != NULL) ? PIC(info->cancelText) : "Cancel", + .token = info->confirmationToken, + .topText = PIC(info->confirmationText), + .style = ROUNDED_AND_FOOTER_STYLE, + .tuneId = info->tuneId}; layoutDescription.modal = info->modal; layoutDescription.withLeftBorder = true; @@ -431,26 +454,10 @@ nbgl_page_t *nbgl_pageDrawConfirmation(nbgl_layoutTouchCallback_t layoutDescription.ticker.tickerCallback = NULL; layout = nbgl_layoutGet(&layoutDescription); - if (info->cancelText == NULL) { - nbgl_layoutButton_t buttonInfo = {.style = BLACK_BACKGROUND, - .text = info->confirmationText, - .icon = NULL, - .token = info->confirmationToken, - .fittingContent = false, - .tuneId = info->tuneId, - .onBottom = true}; - nbgl_layoutAddBottomButton(layout, PIC(&CLOSE_ICON), info->cancelToken, true, info->tuneId); - nbgl_layoutAddButton(layout, &buttonInfo); - } - else { - nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = PIC(info->cancelText), - .token = info->confirmationToken, - .topText = PIC(info->confirmationText), - .style = ROUNDED_AND_FOOTER_STYLE, - .tuneId = info->tuneId}; - addEmptyHeader(layout, MEDIUM_CENTERING_HEADER); - nbgl_layoutAddChoiceButtons(layout, &buttonsInfo); - } + + addEmptyHeader(layout, MEDIUM_CENTERING_HEADER); + nbgl_layoutAddChoiceButtons(layout, &buttonsInfo); + nbgl_layoutAddCenteredInfo(layout, &info->centeredInfo); nbgl_layoutDraw(layout); diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index 299fa3281..d5a71c6a5 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -64,7 +64,8 @@ enum { CONFIRM_TOKEN, REJECT_TOKEN, VALUE_ALIAS_TOKEN, - BLIND_WARNING_TOKEN + BLIND_WARNING_TOKEN, + TIP_BOX_TOKEN }; typedef enum { @@ -175,6 +176,10 @@ static nbgl_page_t *modalPageContext; // context for pages static const char *pageTitle; +// context for tip-box +static const char *tipBoxModalTitle; +static nbgl_contentInfoList_t tipBoxInfoList; + // context for navigation use case static nbgl_pageNavigationInfo_t navInfo; static bool forwardNavOnly; @@ -201,6 +206,7 @@ static nbgl_BundleNavContext_t bundleNavContext; static const uint8_t nbMaxElementsPerContentType[] = { #ifdef TARGET_STAX 1, // CENTERED_INFO + 1, // EXTENDED_CENTER 1, // INFO_LONG_PRESS 1, // INFO_BUTTON 1, // TAG_VALUE_LIST (computed dynamically) @@ -212,6 +218,7 @@ static const uint8_t nbMaxElementsPerContentType[] = { 5, // BARS_LIST #else // TARGET_STAX 1, // CENTERED_INFO + 1, // EXTENDED_CENTER 1, // INFO_LONG_PRESS 1, // INFO_BUTTON 1, // TAG_VALUE_LIST (computed dynamically) @@ -236,6 +243,7 @@ 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 displayBlindWarning(nbgl_opType_t opType); +static void displayTipBoxModal(void); static void displaySettingsPage(uint8_t page, bool forceFullRefresh); static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh); static void pageCallback(int token, uint8_t index); @@ -356,17 +364,18 @@ static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectTex } } -static void prepareReviewFirstPage(nbgl_contentCenteredInfo_t *centeredInfo, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle) +static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle) { - centeredInfo->icon = icon; - centeredInfo->text1 = reviewTitle; - centeredInfo->text2 = reviewSubTitle; - centeredInfo->text3 = "Swipe to review"; - centeredInfo->style = LARGE_CASE_GRAY_INFO; - centeredInfo->offsetY = 0; + contentCenter->icon = icon; + contentCenter->title = reviewTitle; + contentCenter->description = reviewSubTitle; + contentCenter->subText = "Swipe to review"; + contentCenter->smallTitle = NULL; + contentCenter->iconHug = 0; + contentCenter->padding = false; } static void prepareReviewLastPage(nbgl_contentInfoLongPress_t *infoLongPress, @@ -548,6 +557,9 @@ static void pageCallback(int token, uint8_t index) & ~(SKIPPABLE_OPERATION | BLIND_OPERATION)); } } + else if (token == TIP_BOX_TOKEN) { + displayTipBoxModal(); + } else { // probably a control provided by caller if (onContentAction != NULL) { onContentAction(token, index, navInfo.activePage); @@ -770,6 +782,11 @@ static bool genericContextPreparePageContent(const nbgl_content_t *p_content, &p_content->content.centeredInfo, sizeof(pageContent->centeredInfo)); break; + case EXTENDED_CENTER: + memcpy(&pageContent->extendedCenter, + &p_content->content.extendedCenter, + sizeof(pageContent->extendedCenter)); + break; case INFO_LONG_PRESS: memcpy(&pageContent->infoLongPress, &p_content->content.infoLongPress, @@ -804,9 +821,11 @@ static bool genericContextPreparePageContent(const nbgl_content_t *p_content, } if (pair->centeredInfo) { - pageContent->type = CENTERED_INFO; - prepareReviewFirstPage( - &pageContent->centeredInfo, pair->valueIcon, pair->item, pair->value); + pageContent->type = EXTENDED_CENTER; + prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter, + pair->valueIcon, + pair->item, + pair->value); // Skip population of nbgl_contentTagValueList_t structure p_tagValueList = NULL; @@ -1163,6 +1182,35 @@ static void displayBlindWarning(nbgl_opType_t opType) nbgl_refresh(); } +// function used to display the modal containing tip-box infos +static void displayTipBoxModal(void) +{ + nbgl_pageNavigationInfo_t info = {.activePage = 0, + .nbPages = 1, + .navType = NAV_WITH_BUTTONS, + .quitToken = QUIT_TOKEN, + .navWithButtons.navToken = NAV_TOKEN, + .navWithButtons.quitButton = false, + .navWithButtons.backButton = true, + .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}; + + if (modalPageContext != NULL) { + nbgl_pageRelease(modalPageContext); + } + modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true); + + nbgl_refreshSpecial(FULL_COLOR_CLEAN_REFRESH); +} + #ifdef NBGL_QRCODE static void displayAddressQRCode(void) { @@ -1601,6 +1649,7 @@ static void useCaseReview(nbgl_operationType_t operationType, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, + const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback, bool isLight) { @@ -1620,9 +1669,20 @@ static void useCaseReview(nbgl_operationType_t operationType, memset(localContentsList, 0, 3 * sizeof(nbgl_content_t)); // First a centered info - STARTING_CONTENT.type = CENTERED_INFO; + STARTING_CONTENT.type = EXTENDED_CENTER; prepareReviewFirstPage( - &STARTING_CONTENT.content.centeredInfo, icon, reviewTitle, reviewSubTitle); + &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle); + if (tipBox != NULL) { + STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon; + STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text; + STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN; + STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL; + tipBoxModalTitle = tipBox->modalTitle; + // the only supported type yet is @ref INFOS_LIST + if (tipBox->type == INFOS_LIST) { + memcpy(&tipBoxInfoList, &tipBox->infos, sizeof(nbgl_contentInfoList_t)); + } + } // Then the tag/value pairs localContentsList[1].type = TAG_VALUE_LIST; @@ -2309,9 +2369,14 @@ void nbgl_useCaseReviewStart(const nbgl_icon_details_t *icon, .topRightStyle = NO_BUTTON_STYLE, .actionButtonText = NULL, .tuneId = TUNE_TAP_CASUAL}; - prepareReviewFirstPage(&info.centeredInfo, icon, reviewTitle, reviewSubTitle); - onQuit = rejectCallback; - onContinue = continueCallback; + info.centeredInfo.icon = icon; + info.centeredInfo.text1 = reviewTitle; + info.centeredInfo.text2 = reviewSubTitle; + info.centeredInfo.text3 = "Swipe to review"; + info.centeredInfo.style = LARGE_CASE_GRAY_INFO; + info.centeredInfo.offsetY = 0; + onQuit = rejectCallback; + onContinue = continueCallback; #ifdef HAVE_PIEZO_SOUND // Play notification sound @@ -2579,6 +2644,44 @@ void nbgl_useCaseReview(nbgl_operationType_t operationType, reviewTitle, reviewSubTitle, finishTitle, + NULL, + choiceCallback, + false); +} + +/** + * @brief Draws a flow of pages of a review. Navigation operates with either swipe or navigation + * keys at bottom right. The last page contains a long-press button with the given finishTitle and + * the given icon. + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed, the last page being a long press one + * + * @param operationType type of operation (Operation, Transaction, Message) + * @param tagValueList list of tag/value pairs + * @param icon icon used on first and last review page + * @param reviewTitle string used in the first review page + * @param reviewSubTitle string to set under reviewTitle (can be NULL) + * @param finishTitle string used in the last review page + * @param tipBox parameter to build a tip-box and necessary modal (can be NULL) + * @param choiceCallback callback called when operation is accepted (param is true) or rejected + * (param is false) + */ +void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + const nbgl_tipBox_t *tipBox, + nbgl_choiceCallback_t choiceCallback) +{ + useCaseReview(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + tipBox, choiceCallback, false); } @@ -2613,6 +2716,7 @@ void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, reviewTitle, reviewSubTitle, finishTitle, + NULL, choiceCallback, true); } @@ -2688,9 +2792,9 @@ void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, memset(localContentsList, 0, 1 * sizeof(nbgl_content_t)); // First a centered info - STARTING_CONTENT.type = CENTERED_INFO; + STARTING_CONTENT.type = EXTENDED_CENTER; prepareReviewFirstPage( - &STARTING_CONTENT.content.centeredInfo, icon, reviewTitle, reviewSubTitle); + &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle); // compute number of pages & fill navigation structure bundleNavContext.reviewStreaming.stepPageNb @@ -2950,10 +3054,10 @@ void nbgl_useCaseAddressReview(const char *address, memset(localContentsList, 0, 3 * sizeof(nbgl_content_t)); // First a centered info - STARTING_CONTENT.type = CENTERED_INFO; + STARTING_CONTENT.type = EXTENDED_CENTER; prepareReviewFirstPage( - &STARTING_CONTENT.content.centeredInfo, icon, reviewTitle, reviewSubTitle); - STARTING_CONTENT.content.centeredInfo.text3 = "Swipe to continue"; + &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle); + STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue"; // Then the address confirmation pages prepareAddressConfirmationPages(