diff --git a/lib_nbgl/doc/mainpage.dox b/lib_nbgl/doc/mainpage.dox index 926e24936..5bdaad758 100644 --- a/lib_nbgl/doc/mainpage.dox +++ b/lib_nbgl/doc/mainpage.dox @@ -1,10 +1,10 @@ #ifdef HAVE_SE_TOUCH -/** @page nbgl_mainpage New BOLOS Graphic API for Stax +/** @page nbgl_mainpage New BOLOS Graphic API for Stax/Flex @section nbgl_mainpage_intro Introduction This documentation describes the different interfaces of NBGL, the library that -is targeted to be integrated in Stax product. +is targeted to be integrated in Stax or Flex product. NBGL Engine is responsible for constructing screens and handling Touchscreen input. diff --git a/lib_nbgl/doc/nbgl_use_case.dox b/lib_nbgl/doc/nbgl_use_case.dox index 53b2c96ac..78391eaa0 100644 --- a/lib_nbgl/doc/nbgl_use_case.dox +++ b/lib_nbgl/doc/nbgl_use_case.dox @@ -51,8 +51,7 @@ A few APIs are available to draw typical Use-Cases, such as: - @ref nbgl_useCaseReviewLight() to draw the pages of a transaction review with a simple button confirmation, when all info are available from the beginning (see @subpage use_case_review_light) - @ref nbgl_useCaseReviewStreamingStart() to draw the pages of a regular coin transaction review, when all info are not available from the beginning (see @subpage use_case_review_streaming) - for address verification: - - @ref nbgl_useCaseAddressConfirmation() to draw an address confirmation page, with a possibility to see it as QR Code (see @subpage use_case_addr_confirm) - - @ref nbgl_useCaseAddressConfirmationExt() to draw an address confirmation page, with a possibility to see it as QR Code and some extra tag/value pairs (see @subpage use_case_addr_confirm_ext) + - @ref nbgl_useCaseAddressReview() to draw an address confirmation page, with a possibility to see it as QR Code and some extra tag/value pairs (see @subpage use_case_addr_review) - for keypad: - @ref nbgl_useCaseKeypadPIN() to draw a default keypad implementation with hidden digits (see @subpage use_case_keypad) - @ref nbgl_useCaseKeypadDigits() to draw a default keypad implementation, showing digits (see @subpage use_case_keypad) @@ -69,6 +68,9 @@ Some APIs have also been kept for backward compatibility, and for some rare case - @ref nbgl_useCaseReviewStart() to draw the cover page of a review (initial page, without data) - @ref nbgl_useCaseStaticReview() to draw the data pages of a regular review, when all info are available from the beginning (all pages but the cover one) - @ref nbgl_useCaseRegularReview() to draw the data pages of a regular review (all pages but the cover one) +- for address verification: + - @ref nbgl_useCaseAddressConfirmation() to draw an address confirmation page, with a possibility to see it as QR Code + - @ref nbgl_useCaseAddressConfirmationExt() to draw an address confirmation page, with a possibility to see it as QR Code and some extra tag/value pairs - for rare reviews: - @ref nbgl_useCaseForwardOnlyReview() to draw the pages of a forward-only review (without back key) - @ref nbgl_useCaseViewDetails() to draw the pages displaying the full value of a given long data of a review @@ -155,6 +157,22 @@ void appMain(void) { } @endcode +@subsubsection use_case_home_settings_with_action Home & Settings screen with action button Use Case + +\image{inline} html UseCase-HomeAction.png "caption" height=300 + +For some rare applications, one may need an action button in the Home screen, to perform either: + +- The main action of the Application +- Or an side-action, as to display an address + +The \b action argument of @ref nbgl_useCaseHomeAndSettings() can be used for that. This structure (@ref nbgl_homeAction_t) +enables to specify: + +- A text & an icon for the button +- A function to be called when the button is touched +- The type of button (either @ref STRONG_HOME_ACTION for main action, in black, or @ref SOFT_HOME_ACTION for side action, in white) + @subsection use_case_confirm Confirmation Use Case \image{inline} html UseCase-Confirm.png "caption" height=300 @@ -447,54 +465,16 @@ void staticReview(void) { } @endcode -@subsection use_case_addr_confirm Address Confirmation Use Case - -\image{inline} html UseCase-AddressConfirmation.png "caption" height=300 - -When an address needs to be confirmed, it can be displayed in a Address Confirmation Use Case, at first as simple page with -the raw address (as text) and a black button/Footer pair to choose to confirm or reject the address. - -An extra button under the raw address enables to open a modal page to see the address as a QR code. - -The @ref nbgl_useCaseAddressConfirmation() function enables to create such a page, with the following parameters: - -- the address to confirm (NULL terminated string) -- a callback called when button or footer is touched (if true, confirm, if false reject) +@subsection use_case_addr_review Address Review Use Case -Here is the code to display something similar to example picture: +\image{inline} html UseCase-AddressReview.png "caption" height=500 -@code -// called when either confirm button or reject token is called -static void displayAddressCallback(bool confirm) { - if (confirm) { - nbgl_useCaseStatus("Address\nVerified",true,app_fullEthereum); - } - else { - nbgl_useCaseStatus("Address rejected",false,app_fullEthereum); - } -} - -// called when tapping on review start page to actually display address -static void displayAddr(void) { - nbgl_useCaseAddressConfirmation("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", &displayAddressCallback); -} - -void app_ethereumVerifyAddress(void) { - nbgl_useCaseReviewStart(myAppIcon,"Verify MyCoin\naddress",NULL,"Cancel", - displayAddr, appMain); -} -@endcode - -@subsection use_case_addr_confirm_ext Extended Address Confirmation Use Case - -\image{inline} html UseCase-AddressConfirmationExt.png "caption" height=500 - -When an address needs to be confirmed, it can be displayed in a Address Confirmation Use Case, at first as simple page with -the raw address (as text). An extra button under the raw address enables to open a modal page to see the address as a QR code. +When an address needs to be confirmed, it can be displayed in a Address Review Use Case. +After a title page, a second page is displayed with the raw address (as text). An extra button under the raw address enables to open a modal page to see the address as a QR code. Moreover, if extra information need to be displayed, for example a derivation path, it is provided in a second page, also containing a black button/Footer pair to choose to confirm or reject the address. -The @ref nbgl_useCaseAddressConfirmationExt() function enables to create such a set of pages, with the following parameters: +The @ref nbgl_useCaseAddressReview() function enables to create such a set of pages, with the following parameters: - the address to confirm (NULL terminated string) - a callback called when button or footer is touched (if true, confirm, if false reject) @@ -522,14 +502,13 @@ static void displayAddressCallback(bool confirm) { } } -// called when tapping on review start page to actually display address -static void displayAddr(void) { - nbgl_useCaseAddressConfirmationExt("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", &displayAddressCallback, &pairList); -} - void app_ethereumVerifyAddress(void) { - nbgl_useCaseReviewStart(myAppIcon,"Verify MyCoin\naddress",NULL,"Cancel", - displayAddr, appMain); + nbgl_useCaseAddressReview("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", + &pairList + myAppIcon, + "Verify MyCoin\naddress", + NULL,"Cancel", + appMain); } @endcode diff --git a/lib_nbgl/doc/resources/UseCase-AddressConfirmation.png b/lib_nbgl/doc/resources/UseCase-AddressConfirmation.png deleted file mode 100755 index 3f4cc7e12..000000000 Binary files a/lib_nbgl/doc/resources/UseCase-AddressConfirmation.png and /dev/null differ diff --git a/lib_nbgl/doc/resources/UseCase-AddressConfirmationExt.png b/lib_nbgl/doc/resources/UseCase-AddressConfirmationExt.png deleted file mode 100755 index 38f0f113b..000000000 Binary files a/lib_nbgl/doc/resources/UseCase-AddressConfirmationExt.png and /dev/null differ diff --git a/lib_nbgl/doc/resources/UseCase-AddressReview.png b/lib_nbgl/doc/resources/UseCase-AddressReview.png new file mode 100755 index 000000000..1365fa718 Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-AddressReview.png differ diff --git a/lib_nbgl/doc/resources/UseCase-Home.png b/lib_nbgl/doc/resources/UseCase-Home.png deleted file mode 100755 index e90915123..000000000 Binary files a/lib_nbgl/doc/resources/UseCase-Home.png and /dev/null differ diff --git a/lib_nbgl/doc/resources/UseCase-HomeAction.png b/lib_nbgl/doc/resources/UseCase-HomeAction.png new file mode 100755 index 000000000..e82428611 Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-HomeAction.png differ diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h index c74199d5c..0fd7dca6d 100644 --- a/lib_nbgl/include/nbgl_layout.h +++ b/lib_nbgl/include/nbgl_layout.h @@ -277,21 +277,24 @@ typedef struct { * */ typedef enum { - ROUNDED_AND_FOOTER_STYLE - = 0, ///< A rounded black background full width button on top of a footer - BOTH_ROUNDED_STYLE ///< A rounded black background full width button on top of a rounded white - ///< background full width button + ROUNDED_AND_FOOTER_STYLE = 0, ///< A black background button on top of a footer + STRONG_ACTION_AND_FOOTER_STYLE, ///< A black button on top of a footer, with a separation line + SOFT_ACTION_AND_FOOTER_STYLE ///< A white button on top of a footer, with a separation line } nbgl_layoutChoiceButtonsStyle_t; +// for backward compatibility +#define BOTH_ROUNDED_STYLE SOFT_ACTION_AND_FOOTER_STYLE + /** * @brief This structure contains info to build a pair of buttons, one on top of the other. * * @note the pair of button is automatically put on bottom of screen, in the footer */ typedef struct { - const char *topText; ///< up-button text (index 0) - const char *bottomText; ///< bottom-button text (index 1) - uint8_t token; ///< the token that will be used as argument of the callback + const char *topText; ///< up-button text (index 0) + const char *bottomText; ///< bottom-button text (index 1) + const nbgl_icon_details_t *topIcon; ///< icon of top button + uint8_t token; ///< the token that will be used as argument of the callback nbgl_layoutChoiceButtonsStyle_t style; ///< the style of the pair #ifdef HAVE_PIEZO_SOUND tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played @@ -501,7 +504,7 @@ typedef struct { } textAndNav; ///< if type is @ref FOOTER_TEXT_AND_NAV nbgl_layoutNavigationBar_t navigation; ///< if type is @ref FOOTER_NAV nbgl_layoutButton_t button; ///< if type is @ref FOOTER_SIMPLE_BUTTON - nbgl_layoutChoiceButtons_t choiceButtons; ///< if type is @ref FOOTER_SIMPLE_BUTTON + nbgl_layoutChoiceButtons_t choiceButtons; ///< if type is @ref FOOTER_CHOICE_BUTTONS }; } nbgl_layoutFooter_t; diff --git a/lib_nbgl/include/nbgl_page.h b/lib_nbgl/include/nbgl_page.h index 5edfbff9e..26f97c8a9 100644 --- a/lib_nbgl/include/nbgl_page.h +++ b/lib_nbgl/include/nbgl_page.h @@ -197,7 +197,9 @@ typedef struct nbgl_pageInfoDescription_s { uint8_t tapActionToken; ///< the token that will be used as argument of the onActionCallback, ///< when tapped or swiped const char - *actionButtonText; ///< if not NULL a black "action" button is set under the centered info + *actionButtonText; ///< if not NULL an "action" button is set under the centered info + const nbgl_icon_details_t *actionButtonIcon; ///< potential icon of "action" button + nbgl_layoutButtonStyle_t actionButtonStyle; ///< style of "action" button tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when button/footer is pressed } nbgl_pageInfoDescription_t; diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index f926f3378..653fcaa12 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -153,10 +153,24 @@ typedef struct { uint8_t nbContents; ///< number of contents } nbgl_genericContents_t; +/** + * @brief The different types of action button in Home Screen + * + */ +typedef enum { + STRONG_HOME_ACTION = 0, ///< Black button, implicating the main action of the App + SOFT_HOME_ACTION ///< White button, more for extended features +} nbgl_homeActionStyle_t; + +/** + * @brief Structure describing the action button in Home Screen + * + */ typedef struct { const char *text; ///< text to use in action button in Home page const nbgl_icon_details_t *icon; ///< icon to use in action button in Home page - nbgl_callback_t callback; ///< function to call when action button is touched in Home page + nbgl_callback_t callback; ///< function to call when action button is touched in Home page + nbgl_homeActionStyle_t style; ///< style of action button } nbgl_homeAction_t; /** diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c index a300095e9..c0f9caeeb 100644 --- a/lib_nbgl/src/nbgl_layout.c +++ b/lib_nbgl/src/nbgl_layout.c @@ -40,19 +40,23 @@ #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 FOOTER_BUTTON_HEIGHT 128 -#define UP_FOOTER_BUTTON_HEIGHT 120 +#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 +#define ROUNDED_AND_FOOTER_FOOTER_HEIGHT 192 +#define ACTION_AND_FOOTER_FOOTER_HEIGHT 216 #else // TARGET_STAX -#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 +#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 +#define ROUNDED_AND_FOOTER_FOOTER_HEIGHT 208 +#define ACTION_AND_FOOTER_FOOTER_HEIGHT 232 #endif // TARGET_STAX // refresh period of the spinner, in ms @@ -1765,6 +1769,7 @@ int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceBu footerDesc.choiceButtons.bottomText = info->bottomText; footerDesc.choiceButtons.token = info->token; footerDesc.choiceButtons.topText = info->topText; + footerDesc.choiceButtons.topIcon = info->topIcon; footerDesc.choiceButtons.style = info->style; footerDesc.choiceButtons.tuneId = info->tuneId; return nbgl_layoutAddExtendedFooter(layout, &footerDesc); @@ -2749,7 +2754,7 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ return -1; } - // create bottomButton (in white) at first + // create bottom button (footer) at first button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) button, @@ -2761,31 +2766,25 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ // associate with with index 1 obj->index = 1; // put at the bottom of the container - button->obj.alignment = BOTTOM_MIDDLE; - if (footerDesc->choiceButtons.style == ROUNDED_AND_FOOTER_STYLE) { - button->obj.alignmentMarginY = 4; // 4 pixels from screen bottom - button->borderColor = WHITE; - } - else if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) { - button->obj.alignmentMarginY = 4; // 4 pixels from screen bottom - button->borderColor = WHITE; // not a real round button on Flex - } - button->innerColor = WHITE; - button->foregroundColor = BLACK; - button->obj.area.width = AVAILABLE_WIDTH; - button->obj.area.height = BUTTON_DIAMETER; - button->radius = BUTTON_RADIUS; - button->text = PIC(footerDesc->choiceButtons.bottomText); - button->fontId = SMALL_BOLD_FONT; - button->obj.touchMask = (1 << TOUCHED); - button->obj.touchId = CHOICE_2_ID; + button->obj.alignment = BOTTOM_MIDDLE; + button->obj.alignmentMarginY = 4; // 4 pixels from screen bottom + button->borderColor = WHITE; + button->innerColor = WHITE; + button->foregroundColor = BLACK; + button->obj.area.width = AVAILABLE_WIDTH; + button->obj.area.height = BUTTON_DIAMETER; + button->radius = BUTTON_RADIUS; + button->text = PIC(footerDesc->choiceButtons.bottomText); + button->fontId = SMALL_BOLD_FONT; + button->obj.touchMask = (1 << TOUCHED); + button->obj.touchId = CHOICE_2_ID; // add to bottom container layoutInt->footerContainer->children[layoutInt->footerContainer->nbChildren] = (nbgl_obj_t *) button; layoutInt->footerContainer->nbChildren++; // add line if needed - if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) { + if (footerDesc->choiceButtons.style != ROUNDED_AND_FOOTER_STYLE) { line = createHorizontalLine(layoutInt->layer); line->obj.alignment = TOP_MIDDLE; line->obj.alignmentMarginY = 4; @@ -2795,7 +2794,7 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ layoutInt->footerContainer->nbChildren++; } - // then black button, on top of it + // then top button, on top of it button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) button, @@ -2807,8 +2806,8 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ // associate with with index 0 obj->index = 0; button->obj.alignment = TOP_MIDDLE; - button->obj.alignmentMarginY = 24; // 12 pixels from bottom button - if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) { + button->obj.alignmentMarginY = BOTTOM_BORDER_MARGIN; // 24 pixels from top of container + if (footerDesc->choiceButtons.style == SOFT_ACTION_AND_FOOTER_STYLE) { button->innerColor = WHITE; button->borderColor = LIGHT_GRAY; button->foregroundColor = BLACK; @@ -2822,6 +2821,9 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ button->obj.area.height = BUTTON_DIAMETER; button->radius = BUTTON_RADIUS; button->text = PIC(footerDesc->choiceButtons.topText); + button->icon = (footerDesc->choiceButtons.style != ROUNDED_AND_FOOTER_STYLE) + ? PIC(footerDesc->choiceButtons.topIcon) + : NULL; button->fontId = SMALL_BOLD_FONT; button->obj.touchMask = (1 << TOUCHED); button->obj.touchId = CHOICE_1_ID; @@ -2830,21 +2832,12 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ = (nbgl_obj_t *) button; layoutInt->footerContainer->nbChildren++; -#ifdef TARGET_STAX - if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) { - layoutInt->footerContainer->obj.area.height = 232; + if (footerDesc->choiceButtons.style != ROUNDED_AND_FOOTER_STYLE) { + layoutInt->footerContainer->obj.area.height = ACTION_AND_FOOTER_FOOTER_HEIGHT; } else { - layoutInt->footerContainer->obj.area.height = 192; + layoutInt->footerContainer->obj.area.height = ROUNDED_AND_FOOTER_FOOTER_HEIGHT; } -#else // TARGET_STAX - if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) { - layoutInt->footerContainer->obj.area.height = 232; - } - else { - layoutInt->footerContainer->obj.area.height = 208; - } -#endif // TARGET_STAX break; } diff --git a/lib_nbgl/src/nbgl_page.c b/lib_nbgl/src/nbgl_page.c index cf99d9b4c..25f0a2a05 100644 --- a/lib_nbgl/src/nbgl_page.c +++ b/lib_nbgl/src/nbgl_page.c @@ -362,9 +362,9 @@ nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionC // if action button but not QUIT_APP_TEXT bottom button, use a small black button if ((info->actionButtonText != NULL) && (info->bottomButtonStyle != QUIT_APP_TEXT)) { nbgl_layoutButton_t buttonInfo = {.fittingContent = true, - .icon = NULL, + .icon = info->actionButtonIcon, .onBottom = false, - .style = BLACK_BACKGROUND, + .style = info->actionButtonStyle, .text = info->actionButtonText, .token = info->bottomButtonsToken, .tuneId = info->tuneId}; @@ -397,7 +397,11 @@ nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionC .bottomText = "Quit app", .token = info->bottomButtonsToken, .style = BOTH_ROUNDED_STYLE, - .tuneId = info->tuneId}; + .tuneId = info->tuneId, + .topIcon = info->actionButtonIcon}; + buttonsInfo.style = (info->actionButtonStyle == BLACK_BACKGROUND) + ? STRONG_ACTION_AND_FOOTER_STYLE + : SOFT_ACTION_AND_FOOTER_STYLE; nbgl_layoutAddChoiceButtons(layout, &buttonsInfo); } else { diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index a0b908b2e..508cc18c7 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -152,8 +152,7 @@ typedef struct { const char *tagline; const nbgl_genericContents_t *settingContents; const nbgl_contentInfoList_t *infosList; - const char *actionText; - nbgl_callback_t actionCallback; + nbgl_homeAction_t homeAction; nbgl_callback_t quitCallback; } nbgl_homeAndSettingsContext_t; @@ -287,6 +286,13 @@ static void useCaseReviewStreamingStart(nbgl_operationType_t operationType const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback, bool playNotifSound); +static void useCaseHomeExt(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + bool withSettings, + nbgl_homeAction_t *homeAction, + nbgl_callback_t topRightCallback, + nbgl_callback_t quitCallback); static void reset_callbacks(void) { @@ -1603,14 +1609,13 @@ static void bundleNavStartHome(void) { nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings; - nbgl_useCaseHomeExt(context->appName, - context->appIcon, - context->tagline, - context->settingContents != NULL ? true : false, - context->actionText, - context->actionCallback, - bundleNavStartSettings, - context->quitCallback); + useCaseHomeExt(context->appName, + context->appIcon, + context->tagline, + context->settingContents != NULL ? true : false, + &context->homeAction, + bundleNavStartSettings, + context->quitCallback); } static void bundleNavStartSettingsAtPage(uint8_t initSettingPage) @@ -1856,6 +1861,97 @@ static void useCaseReviewStreamingStart(nbgl_operationType_t operationType displayGenericContextPage(0, true); } +/** + * @brief draws the extended version of home page of an app (page on which we land when launching it + * from dashboard) + * @note it enables to use an action button (black on Stax, white on Flex) + * + * @param appName app name + * @param appIcon app icon + * @param tagline text under app name (if NULL, it will be "This app enables signing transactions on + * the network.") + * @param withSettings if true, use a "settings" (wheel) icon in bottom button, otherwise a "info" + * (i) + * @param homeAction if not NULL, structure used for an action button (on top of "Quit + * App" button/footer) + * @param topRightCallback callback called when top-right button is touched + * @param quitCallback callback called when quit button is touched + */ +static void useCaseHomeExt(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + bool withSettings, + nbgl_homeAction_t *homeAction, + nbgl_callback_t topRightCallback, + nbgl_callback_t quitCallback) +{ + reset_callbacks(); + + nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon, + .centeredInfo.text1 = appName, + .centeredInfo.text3 = NULL, + .centeredInfo.style = LARGE_CASE_INFO, + .centeredInfo.offsetY = 0, + .footerText = NULL, + .bottomButtonStyle = QUIT_APP_TEXT, + .tapActionText = NULL, + .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON, + .topRightToken = CONTINUE_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + if ((homeAction->text != NULL) || (homeAction->icon != NULL)) { + // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish + info.bottomButtonsToken = ACTION_BUTTON_TOKEN; + onAction = homeAction->callback; + info.actionButtonText = homeAction->text; + info.actionButtonIcon = homeAction->icon; + info.actionButtonStyle + = (homeAction->style == STRONG_HOME_ACTION) ? BLACK_BACKGROUND : WHITE_BACKGROUND; + } + else { + info.bottomButtonsToken = QUIT_TOKEN; + onAction = NULL; + info.actionButtonText = NULL; + info.actionButtonIcon = NULL; + } + if (tagline == NULL) { + if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) { + snprintf(appDescription, + APP_DESCRIPTION_MAX_LEN, + "This app enables signing\ntransactions on its network."); + } + else { + snprintf(appDescription, + APP_DESCRIPTION_MAX_LEN, + "%s %s\n%s", + TAGLINE_PART1, + appName, + TAGLINE_PART2); + } + + // If there is more than 3 lines, it means the appName was split, so we put it on the next + // line + if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, appDescription, AVAILABLE_WIDTH, false) + > 3) { + snprintf(appDescription, + APP_DESCRIPTION_MAX_LEN, + "%s\n%s %s", + TAGLINE_PART1, + appName, + TAGLINE_PART2); + } + info.centeredInfo.text2 = appDescription; + } + else { + info.centeredInfo.text2 = tagline; + } + + onContinue = topRightCallback; + onQuit = quitCallback; + + pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info); + nbgl_refreshSpecial(FULL_COLOR_CLEAN_REFRESH); +} + /********************** * GLOBAL FUNCTIONS **********************/ @@ -2215,68 +2311,13 @@ void nbgl_useCaseHomeExt(const char *appName, nbgl_callback_t topRightCallback, nbgl_callback_t quitCallback) { - reset_callbacks(); + nbgl_homeAction_t homeAction = {.callback = actionCallback, + .icon = NULL, + .style = STRONG_HOME_ACTION, + .text = actionButtonText}; - nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon, - .centeredInfo.text1 = appName, - .centeredInfo.text3 = NULL, - .centeredInfo.style = LARGE_CASE_INFO, - .centeredInfo.offsetY = 0, - .footerText = NULL, - .bottomButtonStyle = QUIT_APP_TEXT, - .tapActionText = NULL, - .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON, - .topRightToken = CONTINUE_TOKEN, - .actionButtonText = actionButtonText, - .tuneId = TUNE_TAP_CASUAL}; - if (actionButtonText != NULL) { - // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish - info.bottomButtonsToken = ACTION_BUTTON_TOKEN; - onAction = actionCallback; - } - else { - info.bottomButtonsToken = QUIT_TOKEN; - onAction = actionCallback; - } - if (tagline == NULL) { - if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) { - snprintf(appDescription, - APP_DESCRIPTION_MAX_LEN, - "This app enables signing\ntransactions on its network."); - } - else { - snprintf(appDescription, - APP_DESCRIPTION_MAX_LEN, - "%s %s\n%s", - TAGLINE_PART1, - appName, - TAGLINE_PART2); - } - - // If there is more than 3 lines, it means the appName was split, so we put it on the next - // line - if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, appDescription, AVAILABLE_WIDTH, false) - > 3) { - snprintf(appDescription, - APP_DESCRIPTION_MAX_LEN, - "%s\n%s %s", - TAGLINE_PART1, - appName, - TAGLINE_PART2); - } - info.centeredInfo.text2 = appDescription; - } - else { - info.centeredInfo.text2 = tagline; - } - - onContinue = topRightCallback; - onQuit = quitCallback; - if (actionButtonText != NULL) { - info.centeredInfo.offsetY -= 40; - } - pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info); - nbgl_refreshSpecial(FULL_COLOR_CLEAN_REFRESH); + useCaseHomeExt( + appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback); } /** @@ -2482,12 +2523,10 @@ void nbgl_useCaseHomeAndSettings( context->settingContents = settingContents; context->infosList = infosList; if (action != NULL) { - context->actionText = action->text; - context->actionCallback = action->callback; + memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t)); } else { - context->actionText = NULL; - context->actionCallback = NULL; + memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t)); } context->quitCallback = quitCallback;