diff --git a/Makefile.rules b/Makefile.rules index a625fc23b..b45f41c5a 100644 --- a/Makefile.rules +++ b/Makefile.rules @@ -19,7 +19,7 @@ ifeq ($(USE_NBGL),0) SDK_SOURCE_PATH += lib_bagl lib_ux else - SDK_SOURCE_PATH += lib_nbgl lib_ux_nbgl + SDK_SOURCE_PATH += lib_nbgl lib_ux_nbgl lib_ux_sync endif define uniq = diff --git a/lib_nbgl/doc/nbgl_use_case.dox b/lib_nbgl/doc/nbgl_use_case.dox index 1a24e30f4..99753c22f 100644 --- a/lib_nbgl/doc/nbgl_use_case.dox +++ b/lib_nbgl/doc/nbgl_use_case.dox @@ -560,7 +560,7 @@ Here is the code to display something similar to example picture: // 4 pairs of tag/value to display static nbgl_layoutTagValue_t pairs[4]; -static nbgl_layoutTagValueList_t pairList = { +static nbgl_contentTagValueList_t pairList = { .nbMaxLinesForValue = 0, .nbPairs = 4, .pairs = (nbgl_layoutTagValue_t*)pairs @@ -604,7 +604,7 @@ static nbgl_layoutTagValue_t pair; static nbgl_layoutTagValue_t* getPair(uint8_t index); -static nbgl_layoutTagValueList_t pairList = { +static nbgl_contentTagValueList_t pairList = { .nbMaxLinesForValue = 0, .nbPairs = 4, .pairs = NULL, // to indicate that callback should be used @@ -831,7 +831,7 @@ Here is the code to display something similar to example picture: // 2 pairs of tag/value to display in second page static nbgl_layoutTagValue_t pairs[2]; -static nbgl_layoutTagValueList_t pairList = { +static nbgl_contentTagValueList_t pairList = { .nbMaxLinesForValue = 0, .nbPairs = 2, .pairs = (nbgl_layoutTagValue_t*)pairs diff --git a/lib_nbgl/doc/nbgl_use_case_nanos.dox b/lib_nbgl/doc/nbgl_use_case_nanos.dox index 1c1107178..1ad53d45d 100644 --- a/lib_nbgl/doc/nbgl_use_case_nanos.dox +++ b/lib_nbgl/doc/nbgl_use_case_nanos.dox @@ -355,7 +355,7 @@ static nbgl_layoutTagValue_t pairs[2] = { } }; -static nbgl_layoutTagValueList_t pairList = { +static nbgl_contentTagValueList_t pairList = { .nbMaxLinesForValue = 0, .nbPairs = 2, .pairs = (nbgl_layoutTagValue_t*)pairs @@ -392,7 +392,7 @@ static nbgl_layoutTagValue_t pair; static nbgl_layoutTagValue_t* getPair(uint8_t index); -static nbgl_layoutTagValueList_t pairList = { +static nbgl_contentTagValueList_t pairList = { .nbMaxLinesForValue = 0, .nbPairs = 5, .pairs = NULL, // to indicate that callback should be used diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h index 6161a3220..11a6dc619 100644 --- a/lib_nbgl/include/nbgl_layout.h +++ b/lib_nbgl/include/nbgl_layout.h @@ -104,10 +104,15 @@ typedef struct { uint8_t token; ///< the token that will be used as argument of the callback uint8_t nbPages; ///< number of pages. (if 0, no navigation) uint8_t activePage; ///< index of active page (from 0 to nbPages-1). - bool withExitKey; ///< if set to true, an exit button is drawn + bool withExitKey; ///< if set to true, an exit button is drawn (X on the left) bool withBackKey; ///< if set to true, the "back" key is drawn bool withSeparationLine; ///< if set to true, an horizontal line is drawn on top of bar in ///< light gray + bool withPageIndicator; ///< on Flex, a "page on nb_pages" text can be added between back and + ///< forward keys + bool visibleIndicator; ///< on Flex, the page indicator can be visible or not. + ///< if withPageIndicator is true and this boolean false, the back key + ///< is placed as if there was an indicator #ifdef HAVE_PIEZO_SOUND tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when pressing keys) #endif // HAVE_PIEZO_SOUND diff --git a/lib_nbgl/include/nbgl_page.h b/lib_nbgl/include/nbgl_page.h index 1885185ae..64af7ec84 100644 --- a/lib_nbgl/include/nbgl_page.h +++ b/lib_nbgl/include/nbgl_page.h @@ -106,10 +106,12 @@ typedef struct nbgl_pageNavWithTap_s { * */ typedef struct nbgl_pageNavWithButtons_s { - bool quitButton; ///< if set to true, a quit button (X) is displayed in the nav bar - bool backButton; ///< if set to true, a back button (<-) is displayed in the nav bar - uint8_t navToken; ///< the token used as argument of the actionCallback when the nav buttons - ///< are pressed (index param gives the page) + bool quitButton; ///< if set to true, a quit button (X) is displayed in the nav bar + bool backButton; ///< if set to true, a back button (<-) is displayed in the nav bar + bool + visiblePageIndicator; ///< if set to true, the page indicator will be visible in navigation + uint8_t navToken; ///< the token used as argument of the actionCallback when the nav buttons + ///< are pressed (index param gives the page) const char *quitText; ///< the text displayed in footer (on the left), used to quit (only on Flex) } nbgl_pageNavWithButtons_t; diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index 52dcd3a9c..d6a5d3f13 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -188,20 +188,28 @@ void nbgl_useCaseHomeAndSettings(const char *appName, const nbgl_homeAction_t *action, nbgl_callback_t quitCallback); -void nbgl_useCaseReview(nbgl_operationType_t operationType, - const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - const char *finishTitle, - nbgl_choiceCallback_t choiceCallback); - -void nbgl_useCaseAddressReview(const char *address, - const nbgl_layoutTagValueList_t *additionalTagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - nbgl_choiceCallback_t choiceCallback); +void nbgl_useCaseReview(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback); + +void nbgl_useCaseAddressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + nbgl_choiceCallback_t choiceCallback); void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType, nbgl_callback_t quitCallback); @@ -212,8 +220,8 @@ void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback); -void nbgl_useCaseReviewStreamingContinue(const nbgl_layoutTagValueList_t *tagValueList, - nbgl_choiceCallback_t choiceCallback); +void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, + nbgl_choiceCallback_t choiceCallback); void nbgl_useCaseReviewStreamingFinish(const char *finishTitle, nbgl_choiceCallback_t choiceCallback); @@ -229,11 +237,11 @@ void nbgl_useCaseGenericConfiguration(const char *title, #ifdef HAVE_SE_TOUCH // utils -uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs, - const nbgl_layoutTagValueList_t *tagValueList, - uint8_t startIndex, - bool *requireSpecificDisplay); -uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_layoutTagValueList_t *tagValueList); +uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs, + const nbgl_contentTagValueList_t *tagValueList, + uint8_t startIndex, + bool *requireSpecificDisplay); +uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *tagValueList); // use case drawing void nbgl_useCaseHome(const char *appName, @@ -302,19 +310,19 @@ void nbgl_useCaseForwardOnlyReviewNoSkip(const char *rejectText, nbgl_layoutTouchCallback_t buttonCallback, nbgl_navCallback_t navCallback, nbgl_choiceCallback_t choiceCallback); -void nbgl_useCaseStaticReview(const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_pageInfoLongPress_t *infoLongPress, - const char *rejectText, - nbgl_choiceCallback_t callback); -void nbgl_useCaseStaticReviewLight(const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_pageInfoLongPress_t *infoLongPress, - const char *rejectText, - nbgl_choiceCallback_t callback); +void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList, + const nbgl_pageInfoLongPress_t *infoLongPress, + const char *rejectText, + nbgl_choiceCallback_t callback); +void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList, + const nbgl_pageInfoLongPress_t *infoLongPress, + const char *rejectText, + nbgl_choiceCallback_t callback); void nbgl_useCaseViewDetails(const char *tag, const char *value, bool wrapping); void nbgl_useCaseAddressConfirmation(const char *address, nbgl_choiceCallback_t callback); -void nbgl_useCaseAddressConfirmationExt(const char *address, - nbgl_choiceCallback_t callback, - const nbgl_layoutTagValueList_t *tagValueList); +void nbgl_useCaseAddressConfirmationExt(const char *address, + nbgl_choiceCallback_t callback, + const nbgl_contentTagValueList_t *tagValueList); #ifdef NBGL_KEYPAD void nbgl_useCaseKeypadDigits(const char *title, uint8_t minDigits, @@ -369,21 +377,21 @@ void nbgl_useCaseSettings(uint8_t initPage, nbgl_actionCallback_t actionCallback); void nbgl_useCaseRegularReview(uint8_t initPage, uint8_t nbPages, nbgl_navCallback_t navCallback); void nbgl_useCaseForwardOnlyReview(nbgl_navCallback_t navCallback); -void nbgl_useCaseStaticReview(nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *acceptText, - const char *rejectText, - nbgl_choiceCallback_t callback); +void nbgl_useCaseStaticReview(nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *acceptText, + const char *rejectText, + nbgl_choiceCallback_t callback); void nbgl_useCaseAddressConfirmation(const nbgl_icon_details_t *icon, const char *title, const char *address, nbgl_choiceCallback_t callback); -void nbgl_useCaseAddressConfirmationExt(const nbgl_icon_details_t *icon, - const char *title, - const char *address, - nbgl_choiceCallback_t callback, - const nbgl_layoutTagValueList_t *tagValueList); +void nbgl_useCaseAddressConfirmationExt(const nbgl_icon_details_t *icon, + const char *title, + const char *address, + nbgl_choiceCallback_t callback, + const nbgl_contentTagValueList_t *tagValueList); #endif // HAVE_SE_TOUCH void nbgl_useCaseSpinner(const char *text); #ifdef __cplusplus diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c index e4b28b924..1b2e1c197 100644 --- a/lib_nbgl/src/nbgl_layout.c +++ b/lib_nbgl/src/nbgl_layout.c @@ -203,9 +203,8 @@ static void touchCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) || (eventType == SWIPED_RIGHT)) && (obj->type == CONTAINER)) { #if (!defined(TARGET_STAX) && defined(NBGL_KEYBOARD)) - if ((layout->swipeUsage == SWIPE_USAGE_SUGGESTIONS) - && keyboardSwipeCallback(obj, eventType)) { - // if this swipe event is consumed, return here + if (layout->swipeUsage == SWIPE_USAGE_SUGGESTIONS) { + keyboardSwipeCallback(obj, eventType); return; } #endif // TARGET_STAX @@ -728,9 +727,6 @@ nbgl_layout_t *nbgl_layoutGet(const nbgl_layoutDescription_t *description) memset(layout, 0, sizeof(nbgl_layoutInternal_t)); nbTouchableControls = 0; -#ifdef NBGL_KEYBOARD - keyboardInit(); -#endif // NBGL_KEYBOARD layout->callback = (nbgl_layoutTouchCallback_t) PIC(description->onActionCallback); layout->modal = description->modal; @@ -891,14 +887,15 @@ int nbgl_layoutAddTopRightButton(nbgl_layout_t *layout, int nbgl_layoutAddNavigationBar(nbgl_layout_t *layout, const nbgl_layoutNavigationBar_t *info) { nbgl_layoutFooter_t footerDesc; - footerDesc.type = FOOTER_NAV; - footerDesc.separationLine = info->withSeparationLine; - footerDesc.navigation.activePage = info->activePage; - footerDesc.navigation.nbPages = info->nbPages; - footerDesc.navigation.withExitKey = info->withExitKey; - footerDesc.navigation.withBackKey = info->withBackKey; - footerDesc.navigation.token = info->token; - footerDesc.navigation.tuneId = info->tuneId; + footerDesc.type = FOOTER_NAV; + footerDesc.separationLine = info->withSeparationLine; + footerDesc.navigation.activePage = info->activePage; + footerDesc.navigation.nbPages = info->nbPages; + footerDesc.navigation.withExitKey = info->withExitKey; + footerDesc.navigation.withBackKey = info->withBackKey; + footerDesc.navigation.withPageIndicator = false; + footerDesc.navigation.token = info->token; + footerDesc.navigation.tuneId = info->tuneId; return nbgl_layoutAddExtendedFooter(layout, &footerDesc); } @@ -2577,13 +2574,7 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ navContainer->obj.alignment = BOTTOM_RIGHT; navContainer->obj.area.width = SCREEN_WIDTH - textArea->obj.area.width; navContainer->obj.area.height = SIMPLE_FOOTER_HEIGHT; - layoutNavigationPopulate(navContainer, - footerDesc->textAndNav.navigation.nbPages, - footerDesc->textAndNav.navigation.activePage, - footerDesc->textAndNav.navigation.withExitKey, - footerDesc->textAndNav.navigation.withBackKey, - true, - layoutInt->layer); + layoutNavigationPopulate(navContainer, &footerDesc->navigation, layoutInt->layer); obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) navContainer, footerDesc->textAndNav.navigation.token, @@ -2617,13 +2608,8 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_ layoutInt->footerContainer->obj.area.width = SCREEN_WIDTH; #endif // TARGET_STAX layoutInt->footerContainer->obj.area.height = SIMPLE_FOOTER_HEIGHT; - layoutNavigationPopulate(layoutInt->footerContainer, - footerDesc->navigation.nbPages, - footerDesc->navigation.activePage, - footerDesc->navigation.withExitKey, - footerDesc->navigation.withBackKey, - false, - layoutInt->layer); + layoutNavigationPopulate( + layoutInt->footerContainer, &footerDesc->navigation, layoutInt->layer); layoutInt->footerContainer->nbChildren = 4; obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) layoutInt->footerContainer, diff --git a/lib_nbgl/src/nbgl_layout_internal.h b/lib_nbgl/src/nbgl_layout_internal.h index 9e008fdb0..cbff2384c 100644 --- a/lib_nbgl/src/nbgl_layout_internal.h +++ b/lib_nbgl/src/nbgl_layout_internal.h @@ -86,20 +86,15 @@ typedef struct nbgl_layoutInternal_s { /********************** * GLOBAL PROTOTYPES **********************/ -void keyboardInit(void); bool keyboardSwipeCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType); void layoutAddObject(nbgl_layoutInternal_t *layout, nbgl_obj_t *obj); layoutObj_t *layoutAddCallbackObj(nbgl_layoutInternal_t *layout, nbgl_obj_t *obj, uint8_t token, tune_index_e tuneId); -void layoutNavigationPopulate(nbgl_container_t *navContainer, - uint8_t nbPages, - uint8_t activePage, - bool withExitKey, - bool withBackKey, - bool withPageIndicator, - uint8_t layer); +void layoutNavigationPopulate(nbgl_container_t *navContainer, + const nbgl_layoutNavigationBar_t *navConfig, + uint8_t layer); bool layoutNavigationCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType, uint8_t nbPages, diff --git a/lib_nbgl/src/nbgl_layout_keyboard.c b/lib_nbgl/src/nbgl_layout_keyboard.c index 097979741..338625d72 100644 --- a/lib_nbgl/src/nbgl_layout_keyboard.c +++ b/lib_nbgl/src/nbgl_layout_keyboard.c @@ -33,7 +33,9 @@ enum { PAGE_INDICATOR_INDEX = 0, LEFT_HALF_INDEX, // half disc displayed on the bottom left RIGHT_HALF_INDEX, // half disc displayed on the bottom right - FIRST_BUTTON_INDEX + FIRST_BUTTON_INDEX, + SECOND_BUTTON_INDEX, + NB_SUGGESTION_CHILDREN }; #endif // TARGET_STAX @@ -140,11 +142,6 @@ static bool updateSuggestionButtons(nbgl_container_t *container, * GLOBAL INTERNAL FUNCTIONS **********************/ -void keyboardInit(void) -{ - nbActiveButtons = 0; -} - #ifdef TARGET_FLEX bool keyboardSwipeCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) { @@ -170,6 +167,7 @@ bool keyboardSwipeCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) if (i < (uint32_t) nbActiveButtons) { if (updateSuggestionButtons(suggestionsContainer, eventType, i)) { + io_seproxyhal_play_tune(TUNE_TAP_CASUAL); nbgl_redrawObject((nbgl_obj_t *) suggestionsContainer, NULL, false); nbgl_refreshSpecial(FULL_COLOR_PARTIAL_REFRESH); } @@ -322,8 +320,8 @@ static nbgl_container_t *addSuggestionButtons(nbgl_layoutInternal_t *layoutInt, suggestionsContainer->obj.area.height = SMALL_BUTTON_HEIGHT + 28; // on Flex, the first child is used by the progress indicator, if more that 2 buttons suggestionsContainer->nbChildren = nbActiveButtons + FIRST_BUTTON_INDEX; - suggestionsContainer->children = (nbgl_obj_t **) nbgl_containerPoolGet( - NB_MAX_VISIBLE_SUGGESTION_BUTTONS + 1, layoutInt->layer); + suggestionsContainer->children + = (nbgl_obj_t **) nbgl_containerPoolGet(NB_SUGGESTION_CHILDREN, layoutInt->layer); #endif // TARGET_STAX // put suggestionsContainer at 24px of the bottom of main container @@ -331,7 +329,8 @@ static nbgl_container_t *addSuggestionButtons(nbgl_layoutInternal_t *layoutInt, suggestionsContainer->obj.alignment = BOTTOM_MIDDLE; // create all possible suggestion buttons, even if not displayed at first - nbgl_objPoolGetArray(BUTTON, NB_MAX_SUGGESTION_BUTTONS, 0, (nbgl_obj_t **) &choiceButtons); + nbgl_objPoolGetArray( + BUTTON, NB_MAX_SUGGESTION_BUTTONS, layoutInt->layer, (nbgl_obj_t **) &choiceButtons); for (int i = 0; i < NB_MAX_SUGGESTION_BUTTONS; i++) { obj = layoutAddCallbackObj( layoutInt, (nbgl_obj_t *) choiceButtons[i], firstButtonToken + i, tuneId); @@ -388,7 +387,7 @@ static nbgl_container_t *addSuggestionButtons(nbgl_layoutInternal_t *layoutInt, suggestionsContainer->children[PAGE_INDICATOR_INDEX] = (nbgl_obj_t *) indicator; // also allocate the semi disc that may be displayed on the left or right of the full // buttons - nbgl_objPoolGetArray(IMAGE, 2, 0, (nbgl_obj_t **) &partialButtonImages); + nbgl_objPoolGetArray(IMAGE, 2, layoutInt->layer, (nbgl_obj_t **) &partialButtonImages); partialButtonImages[0]->buffer = &C_left_half_64px; partialButtonImages[0]->obj.alignment = TOP_LEFT; partialButtonImages[0]->foregroundColor = BLACK; diff --git a/lib_nbgl/src/nbgl_layout_keypad.c b/lib_nbgl/src/nbgl_layout_keypad.c index 4f5ca57be..8302f0f88 100644 --- a/lib_nbgl/src/nbgl_layout_keypad.c +++ b/lib_nbgl/src/nbgl_layout_keypad.c @@ -515,12 +515,12 @@ int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, if (nbActiveDigits == container->nbChildren) { return 0; } - // deactivate the next digit + // deactivate the next digit by turning it to white image = (nbgl_image_t *) container->children[nbActiveDigits]; image->foregroundColor = WHITE; } else { - image->buffer = &DIGIT_ICON; + // activate it the last digit by turning it to black image->foregroundColor = BLACK; } } diff --git a/lib_nbgl/src/nbgl_layout_navigation.c b/lib_nbgl/src/nbgl_layout_navigation.c index a69937466..fad6ff34c 100644 --- a/lib_nbgl/src/nbgl_layout_navigation.c +++ b/lib_nbgl/src/nbgl_layout_navigation.c @@ -142,30 +142,17 @@ bool layoutNavigationCallback(nbgl_obj_t *obj, * container * * @param navContainer container used for the objects of the navigation - * @param nbPages max number of pages for navigation (if < 2, no navigation keys) - * @param activePage active page at start-up in [0-(nbPages-1)] - * @param withExitKey if set to true, an exit key is added on the left - * @param withBackKey if set to false, the back key is not drawn - * @param withPageIndicator if set to true, " on " is added between - * navigation arrows (if more than 1 page) + * @param navConfig configuration to create the navigation bar, at the bottom of the screen * @param layer layer (screen) to create the navigation bar in * */ -void layoutNavigationPopulate(nbgl_container_t *navContainer, - uint8_t nbPages, - uint8_t activePage, - bool withExitKey, - bool withBackKey, - bool withPageIndicator, - uint8_t layer) +void layoutNavigationPopulate(nbgl_container_t *navContainer, + const nbgl_layoutNavigationBar_t *navConfig, + uint8_t layer) { nbgl_button_t *button; -#ifdef TARGET_STAX - UNUSED(withPageIndicator); -#endif // TARGET_STAX - - if (withExitKey) { + if (navConfig->withExitKey) { button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layer); button->innerColor = WHITE; button->borderColor = BORDER_COLOR; @@ -173,21 +160,21 @@ void layoutNavigationPopulate(nbgl_container_t *navContainer, button->obj.area.height = BUTTON_DIAMETER; button->radius = BUTTON_RADIUS; button->icon = &CLOSE_ICON; -#ifndef TARGET_STAX - button->obj.alignmentMarginX = (nbPages > 1) ? 8 : 0; -#endif // TARGET_STAX +#ifdef TARGET_FLEX + button->obj.alignmentMarginX = (navConfig->nbPages > 1) ? 8 : 0; +#endif // TARGET_FLEX - button->obj.alignment = (nbPages > 1) ? MID_LEFT : CENTER; + button->obj.alignment = (navConfig->nbPages > 1) ? MID_LEFT : CENTER; button->obj.touchMask = (1 << TOUCHED); button->obj.touchId = BOTTOM_BUTTON_ID; navContainer->children[EXIT_BUTTON_INDEX] = (nbgl_obj_t *) button; } // create previous page button (back) - if (withBackKey) { + if (navConfig->withBackKey) { button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layer); button->innerColor = WHITE; button->borderColor = BORDER_COLOR; - if (withExitKey) { + if (navConfig->withExitKey) { button->obj.area.width = NAV_BUTTON_WIDTH; } else { @@ -198,7 +185,7 @@ void layoutNavigationPopulate(nbgl_container_t *navContainer, #ifdef TARGET_STAX button->icon = &LEFT_ARROW_ICON; // align either on the right of Exit key, or on the inner left of the container - if (withExitKey) { + if (navConfig->withExitKey) { button->obj.alignmentMarginX = INTERNAL_SMALL_MARGIN; button->obj.alignment = MID_RIGHT; button->obj.alignTo = navContainer->children[EXIT_BUTTON_INDEX]; @@ -222,7 +209,7 @@ void layoutNavigationPopulate(nbgl_container_t *navContainer, button->innerColor = WHITE; button->borderColor = BORDER_COLOR; button->foregroundColor = BLACK; - if (withExitKey) { + if (navConfig->withExitKey) { button->obj.area.width = NAV_BUTTON_WIDTH; } else { @@ -243,32 +230,33 @@ void layoutNavigationPopulate(nbgl_container_t *navContainer, button->obj.touchId = RIGHT_BUTTON_ID; navContainer->children[NEXT_PAGE_INDEX] = (nbgl_obj_t *) button; - // potentially create page indicator (with a text area) -#ifndef TARGET_STAX - if (withPageIndicator && withBackKey - && (nbPages > 1 && nbPages != NBGL_NO_PROGRESS_INDICATOR)) { - nbgl_text_area_t *textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layer); +#ifdef TARGET_FLEX + // potentially create page indicator (with a text area, and "page of nb_page") + if (navConfig->withPageIndicator) { + if (navConfig->visibleIndicator) { + nbgl_text_area_t *textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layer); - SPRINTF(navText, "%d of %d", activePage + 1, nbPages); + SPRINTF(navText, "%d of %d", navConfig->activePage + 1, navConfig->nbPages); - textArea->obj.alignment = BOTTOM_RIGHT; - textArea->textColor = DARK_GRAY; - textArea->obj.area.width = 109; - textArea->text = navText; - textArea->fontId = SMALL_REGULAR_FONT; - textArea->obj.area.height = NAV_BUTTON_HEIGHT; - textArea->textAlignment = CENTER; - textArea->obj.alignment = MID_RIGHT; - textArea->obj.alignmentMarginX = NAV_BUTTON_WIDTH - 15; - navContainer->children[PAGE_INDICATOR_INDEX] = (nbgl_obj_t *) textArea; - if (withBackKey) { + textArea->obj.alignment = BOTTOM_RIGHT; + textArea->textColor = DARK_GRAY; + textArea->obj.area.width = 109; + textArea->text = navText; + textArea->fontId = SMALL_REGULAR_FONT; + textArea->obj.area.height = NAV_BUTTON_HEIGHT; + textArea->textAlignment = CENTER; + textArea->obj.alignment = MID_RIGHT; + textArea->obj.alignmentMarginX = NAV_BUTTON_WIDTH - 15; + navContainer->children[PAGE_INDICATOR_INDEX] = (nbgl_obj_t *) textArea; + } + if (navConfig->withBackKey) { navContainer->children[PREVIOUS_PAGE_INDEX]->alignmentMarginX += 79; } } -#endif // TARGET_STAX +#endif // TARGET_FLEX // configure enabling/disabling of button - configButtons(navContainer, nbPages, activePage); + configButtons(navContainer, navConfig->nbPages, navConfig->activePage); return; } diff --git a/lib_nbgl/src/nbgl_obj.c b/lib_nbgl/src/nbgl_obj.c index 7c45e5adc..21dfe1bc3 100644 --- a/lib_nbgl/src/nbgl_obj.c +++ b/lib_nbgl/src/nbgl_obj.c @@ -23,7 +23,11 @@ /********************* * DEFINES *********************/ +#ifdef TARGET_STAX #define NB_MAX_PAGES_WITH_DASHES 10 +#else // TARGET_STAX +#define NB_MAX_PAGES_WITH_DASHES 6 +#endif // TARGET_STAX // max number of letters in TEXT_ENTRY #define NB_MAX_LETTERS 9 @@ -734,7 +738,11 @@ static void draw_pageIndicator(nbgl_page_indicator_t *obj, if (obj->nbPages <= NB_MAX_PAGES_WITH_DASHES) { int i; +#ifdef TARGET_STAX #define INTER_DASHES 10 // pixels +#else // TARGET_STAX +#define INTER_DASHES 8 // pixels +#endif // TARGET_STAX // force height obj->obj.area.height = 4; diff --git a/lib_nbgl/src/nbgl_page.c b/lib_nbgl/src/nbgl_page.c index 258ae284e..4db978475 100644 --- a/lib_nbgl/src/nbgl_page.c +++ b/lib_nbgl/src/nbgl_page.c @@ -105,29 +105,18 @@ static void addContent(nbgl_pageContent_t *content, = {.type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = 40}; nbgl_layoutAddHeader(layout, &headerDesc); } - uint16_t nbLines = nbgl_getTextNbLinesInWidth( - content->tagValueDetails.tagValueList.smallCaseForValue ? SMALL_BOLD_FONT - : LARGE_MEDIUM_FONT, - content->tagValueDetails.tagValueList.pairs[0].value, - SCREEN_WIDTH - 2 * BORDER_MARGIN, - content->tagValueDetails.tagValueList.wrapping); - // automatically display a button if content is longer that nbMaxLinesForValue - if (nbLines > (content->tagValueDetails.tagValueList.nbMaxLinesForValue)) { - nbgl_layoutButton_t buttonInfo; - content->tagValueDetails.tagValueList.nbMaxLinesForValue -= 3; - nbgl_layoutAddTagValueList(layout, &content->tagValueDetails.tagValueList); - buttonInfo.fittingContent = true; - buttonInfo.icon = content->tagValueDetails.detailsButtonIcon; - buttonInfo.style = WHITE_BACKGROUND; - buttonInfo.text = content->tagValueDetails.detailsButtonText; - buttonInfo.token = content->tagValueDetails.detailsButtonToken; - buttonInfo.tuneId = content->tagValueDetails.tuneId; - buttonInfo.onBottom = false; - nbgl_layoutAddButton(layout, &buttonInfo); - } - else { - nbgl_layoutAddTagValueList(layout, &content->tagValueDetails.tagValueList); - } + // display a button under tag/value + nbgl_layoutButton_t buttonInfo; + content->tagValueDetails.tagValueList.nbMaxLinesForValue -= 3; + nbgl_layoutAddTagValueList(layout, &content->tagValueDetails.tagValueList); + buttonInfo.fittingContent = true; + buttonInfo.icon = content->tagValueDetails.detailsButtonIcon; + buttonInfo.style = WHITE_BACKGROUND; + buttonInfo.text = content->tagValueDetails.detailsButtonText; + buttonInfo.token = content->tagValueDetails.detailsButtonToken; + buttonInfo.tuneId = content->tagValueDetails.tuneId; + buttonInfo.onBottom = false; + nbgl_layoutAddButton(layout, &buttonInfo); break; } case TAG_VALUE_CONFIRM: { @@ -554,13 +543,14 @@ nbgl_page_t *nbgl_pageDrawGenericContentExt(nbgl_layoutTouchCallback_t onA footerDesc.separationLine = true; if (nav->nbPages > 1) { if (nav->navWithButtons.quitText == NULL) { - footerDesc.type = FOOTER_NAV; - footerDesc.navigation.activePage = nav->activePage; - footerDesc.navigation.nbPages = nav->nbPages; - footerDesc.navigation.withExitKey = nav->navWithButtons.quitButton; - footerDesc.navigation.withBackKey = nav->navWithButtons.backButton; - footerDesc.navigation.token = nav->navWithButtons.navToken; - footerDesc.navigation.tuneId = nav->tuneId; + footerDesc.type = FOOTER_NAV; + footerDesc.navigation.activePage = nav->activePage; + footerDesc.navigation.nbPages = nav->nbPages; + footerDesc.navigation.withExitKey = nav->navWithButtons.quitButton; + footerDesc.navigation.withBackKey = nav->navWithButtons.backButton; + footerDesc.navigation.withPageIndicator = false; + footerDesc.navigation.token = nav->navWithButtons.navToken; + footerDesc.navigation.tuneId = nav->tuneId; } else { footerDesc.type = FOOTER_TEXT_AND_NAV; @@ -571,8 +561,11 @@ nbgl_page_t *nbgl_pageDrawGenericContentExt(nbgl_layoutTouchCallback_t onA footerDesc.textAndNav.navigation.nbPages = nav->nbPages; footerDesc.textAndNav.navigation.withExitKey = false; footerDesc.textAndNav.navigation.withBackKey = nav->navWithButtons.backButton; - footerDesc.textAndNav.navigation.token = nav->navWithButtons.navToken; - footerDesc.textAndNav.navigation.tuneId = nav->tuneId; + footerDesc.textAndNav.navigation.visibleIndicator + = nav->navWithButtons.visiblePageIndicator; + footerDesc.textAndNav.navigation.withPageIndicator = true; + footerDesc.textAndNav.navigation.token = nav->navWithButtons.navToken; + footerDesc.textAndNav.navigation.tuneId = nav->tuneId; } } else if (nav->navWithButtons.quitText != NULL) { diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index e788851c1..d44b6a569 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -333,10 +333,12 @@ static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectTex navInfo.navWithTap.backToken = BACK_TOKEN; #else // TARGET_STAX UNUSED(rejectText); - navInfo.navType = NAV_WITH_BUTTONS; - navInfo.navWithButtons.quitText = "Reject"; - navInfo.navWithButtons.navToken = NAV_TOKEN; - navInfo.navWithButtons.backButton = true; + navInfo.navType = NAV_WITH_BUTTONS; + navInfo.navWithButtons.quitText = "Reject"; + navInfo.navWithButtons.navToken = NAV_TOKEN; + navInfo.navWithButtons.backButton + = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true; + navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV); #endif // TARGET_STAX } } @@ -352,7 +354,7 @@ static void prepareReviewFirstPage(nbgl_contentCenteredInfo_t *centeredInfo, #ifdef TARGET_STAX centeredInfo->text3 = NULL; #else // TARGET_STAX - centeredInfo->text3 = "Swipe to continue"; + centeredInfo->text3 = "Swipe to review"; #endif // TARGET_STAX centeredInfo->style = LARGE_CASE_GRAY_INFO; centeredInfo->offsetY = 0; @@ -368,6 +370,16 @@ static void prepareReviewLastPage(nbgl_contentInfoLongPress_t *infoLongPress, infoLongPress->longPressToken = CONFIRM_TOKEN; } +static void prepareReviewLightLastPage(nbgl_contentInfoButton_t *infoButton, + const nbgl_icon_details_t *icon, + const char *finishTitle) +{ + infoButton->text = finishTitle; + infoButton->icon = icon; + infoButton->buttonText = "Approve"; + infoButton->buttonToken = CONFIRM_TOKEN; +} + static const char *getRejectReviewText(nbgl_operationType_t operationType) { #ifdef TARGET_STAX @@ -785,6 +797,10 @@ static bool genericContextPreparePageContent(const nbgl_content_t *p_content, pageContent->type = CENTERED_INFO; prepareReviewFirstPage( &pageContent->centeredInfo, pair->valueIcon, pair->item, pair->value); +#ifdef TARGET_FLEX + // use "Swipe to continue" instead of "Swipe to review" for intermediate pages + pageContent->centeredInfo.text3 = "Swipe to continue"; +#endif // TARGET_FLEX // Skip population of nbgl_contentTagValueList_t structure p_tagValueList = NULL; @@ -1287,10 +1303,10 @@ static uint8_t nbgl_useCaseGetNbPagesForGenericContents( return nbPages; } -static void prepareAddressConfirmationPages(const char *address, - const nbgl_layoutTagValueList_t *tagValueList, - nbgl_content_t *firstPageContent, - nbgl_content_t *secondPageContent) +static void prepareAddressConfirmationPages(const char *address, + const nbgl_contentTagValueList_t *tagValueList, + nbgl_content_t *firstPageContent, + nbgl_content_t *secondPageContent) { nbgl_contentTagValueConfirm_t *tagValueConfirm; @@ -1346,7 +1362,7 @@ static void prepareAddressConfirmationPages(const char *add tagValueConfirm->detailsButtonText = NULL; tagValueConfirm->detailsButtonIcon = NULL; tagValueConfirm->tuneId = TUNE_TAP_CASUAL; - memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_layoutTagValueList_t)); + memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t)); #ifdef TARGET_STAX // no next page @@ -1433,7 +1449,7 @@ static void bundleNavReviewStreamingChoice(bool confirm) { if (confirm) { // Display a spinner if it wasn't the finish step - if (navInfo.nbPages == NBGL_NO_PROGRESS_INDICATOR) { + if (localContentsList[0].type != INFO_LONG_PRESS) { nbgl_useCaseSpinner("Processing"); } bundleNavContext.reviewStreaming.choiceCallback(true); @@ -1460,10 +1476,10 @@ static void bundleNavReviewStreamingChoice(bool confirm) * - the tag/value doesn't fit in a page * @return the number of tag/value pairs fitting in a page */ -uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs, - const nbgl_layoutTagValueList_t *tagValueList, - uint8_t startIndex, - bool *requireSpecificDisplay) +uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs, + const nbgl_contentTagValueList_t *tagValueList, + uint8_t startIndex, + bool *requireSpecificDisplay) { uint8_t nbPairsInPage = 0; uint16_t currentHeight = 12; // upper margin @@ -1538,7 +1554,7 @@ uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPair * @param tagValueList list of tag/value pairs * @return the number of pages necessary to display the given list of tag/value pairs */ -uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_layoutTagValueList_t *tagValueList) +uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *tagValueList) { uint8_t nbPages = 0; uint8_t nbPairs = tagValueList->nbPairs; @@ -2239,10 +2255,10 @@ void nbgl_useCaseForwardOnlyReviewNoSkip(const char *rejectText, * @param callback callback called when transaction is accepted (param is true) or rejected (param * is false) */ -void nbgl_useCaseStaticReview(const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_pageInfoLongPress_t *infoLongPress, - const char *rejectText, - nbgl_choiceCallback_t callback) +void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList, + const nbgl_pageInfoLongPress_t *infoLongPress, + const char *rejectText, + nbgl_choiceCallback_t callback) { uint8_t offset = 0; @@ -2261,7 +2277,7 @@ void nbgl_useCaseStaticReview(const nbgl_layoutTagValueList_t *tagValueList, localContentsList[offset].type = TAG_VALUE_LIST; memcpy(&localContentsList[offset].content.tagValueList, tagValueList, - sizeof(nbgl_layoutTagValueList_t)); + sizeof(nbgl_contentTagValueList_t)); offset++; } @@ -2294,10 +2310,10 @@ void nbgl_useCaseStaticReview(const nbgl_layoutTagValueList_t *tagValueList, * @param callback callback called when transaction is accepted (param is true) or rejected (param * is false) */ -void nbgl_useCaseStaticReviewLight(const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_pageInfoLongPress_t *infoLongPress, - const char *rejectText, - nbgl_choiceCallback_t callback) +void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList, + const nbgl_pageInfoLongPress_t *infoLongPress, + const char *rejectText, + nbgl_choiceCallback_t callback) { uint8_t offset = 0; @@ -2316,7 +2332,7 @@ void nbgl_useCaseStaticReviewLight(const nbgl_layoutTagValueList_t *tagValueList localContentsList[offset].type = TAG_VALUE_LIST; memcpy(&localContentsList[offset].content.tagValueList, tagValueList, - sizeof(nbgl_layoutTagValueList_t)); + sizeof(nbgl_contentTagValueList_t)); offset++; } @@ -2352,13 +2368,13 @@ void nbgl_useCaseStaticReviewLight(const nbgl_layoutTagValueList_t *tagValueList * @param choiceCallback callback called when operation is accepted (param is true) or rejected * (param is false) */ -void nbgl_useCaseReview(nbgl_operationType_t operationType, - const nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - const char *finishTitle, - nbgl_choiceCallback_t choiceCallback) +void nbgl_useCaseReview(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback) { reset_callbacks(); memset(&genericContext, 0, sizeof(genericContext)); @@ -2384,7 +2400,7 @@ void nbgl_useCaseReview(nbgl_operationType_t operationType, localContentsList[1].type = TAG_VALUE_LIST; memcpy(&localContentsList[1].content.tagValueList, tagValueList, - sizeof(nbgl_layoutTagValueList_t)); + sizeof(nbgl_contentTagValueList_t)); // Eventually the long press page localContentsList[2].type = INFO_LONG_PRESS; @@ -2397,6 +2413,63 @@ void nbgl_useCaseReview(nbgl_operationType_t operationType, displayGenericContextPage(0, true); } +/** + * @brief Draws a flow of pages of a light review. A back key is available on top-left of the + * screen, except in first page It is possible to go to next page thanks to "tap to continue". + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed, the last page being a short 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 choiceCallback callback called when operation is accepted (param is true) or rejected + * (param is false) + */ +void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback) +{ + reset_callbacks(); + memset(&genericContext, 0, sizeof(genericContext)); + + // memorize context + onChoice = choiceCallback; + navType = GENERIC_NAV; + pageTitle = NULL; + + genericContext.genericContents.contentsList = localContentsList; + genericContext.genericContents.nbContents = 3; + memset(localContentsList, 0, 3 * sizeof(nbgl_content_t)); + + // First a centered info + localContentsList[0].type = CENTERED_INFO; + prepareReviewFirstPage( + &localContentsList[0].content.centeredInfo, icon, reviewTitle, reviewSubTitle); + + // Then the tag/value pairs + localContentsList[1].type = TAG_VALUE_LIST; + memcpy(&localContentsList[1].content.tagValueList, + tagValueList, + sizeof(nbgl_contentTagValueList_t)); + + // Eventually the long press page + localContentsList[2].type = INFO_BUTTON; + prepareReviewLightLastPage(&localContentsList[2].content.infoButton, icon, finishTitle); + + // compute number of pages & fill navigation structure + uint8_t nbPages = nbgl_useCaseGetNbPagesForGenericContents(&genericContext.genericContents, 0); + prepareNavInfo(true, nbPages, getRejectReviewText(operationType)); + + displayGenericContextPage(0, true); +} + /** * @brief Draws a flow of pages of a review with automatic pagination depending on content * to be displayed that is passed through contents. @@ -2470,6 +2543,10 @@ void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, bundleNavContext.reviewStreaming.stepPageNb = nbgl_useCaseGetNbPagesForGenericContents(&genericContext.genericContents, 0); prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType)); +#ifdef TARGET_FLEX + // no back button on first page + navInfo.navWithButtons.backButton = false; +#endif // TARGET_STAX displayGenericContextPage(0, true); } @@ -2484,8 +2561,8 @@ void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, * @param choiceCallback callback called when more operation data are needed (param is true) or * operation is rejected (param is false) */ -void nbgl_useCaseReviewStreamingContinue(const nbgl_layoutTagValueList_t *tagValueList, - nbgl_choiceCallback_t choiceCallback) +void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, + nbgl_choiceCallback_t choiceCallback) { // Should follow a call to nbgl_useCaseReviewStreamingStart memset(&genericContext, 0, sizeof(genericContext)); @@ -2505,7 +2582,7 @@ void nbgl_useCaseReviewStreamingContinue(const nbgl_layoutTagValueList_t *tagVal localContentsList[0].type = TAG_VALUE_LIST; memcpy(&localContentsList[0].content.tagValueList, tagValueList, - sizeof(nbgl_layoutTagValueList_t)); + sizeof(nbgl_contentTagValueList_t)); // compute number of pages & fill navigation structure bundleNavContext.reviewStreaming.stepPageNb @@ -2621,9 +2698,9 @@ void nbgl_useCaseAddressConfirmation(const char *address, nbgl_choiceCallback_t * @param tagValueList list of tag/value pairs (must fit in a single page, and be persistent because * no copy) */ -void nbgl_useCaseAddressConfirmationExt(const char *address, - nbgl_choiceCallback_t callback, - const nbgl_layoutTagValueList_t *tagValueList) +void nbgl_useCaseAddressConfirmationExt(const char *address, + nbgl_choiceCallback_t callback, + const nbgl_contentTagValueList_t *tagValueList) { reset_callbacks(); memset(&genericContext, 0, sizeof(genericContext)); @@ -2670,12 +2747,12 @@ void nbgl_useCaseAddressConfirmationExt(const char *address * @param choiceCallback callback called when transaction is accepted (param is true) or rejected * (param is false) */ -void nbgl_useCaseAddressReview(const char *address, - const nbgl_layoutTagValueList_t *additionalTagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *reviewSubTitle, - nbgl_choiceCallback_t choiceCallback) +void nbgl_useCaseAddressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + nbgl_choiceCallback_t choiceCallback) { reset_callbacks(); memset(&genericContext, 0, sizeof(genericContext)); diff --git a/lib_nbgl/src/nbgl_use_case_nanos.c b/lib_nbgl/src/nbgl_use_case_nanos.c index 7740308f5..d8201347a 100644 --- a/lib_nbgl/src/nbgl_use_case_nanos.c +++ b/lib_nbgl/src/nbgl_use_case_nanos.c @@ -28,7 +28,7 @@ typedef struct ReviewContext_s { nbgl_navCallback_t onNav; nbgl_choiceCallback_t onChoice; - nbgl_layoutTagValueList_t tagValueList; + nbgl_contentTagValueList_t tagValueList; const nbgl_icon_details_t *icon; const char *reviewTitle; const char *address; // for address confirmation review @@ -406,19 +406,19 @@ void nbgl_useCaseForwardOnlyReview(nbgl_navCallback_t navCallback) * @param callback callback called when transaction is accepted (param is true) or rejected (param * is false) */ -void nbgl_useCaseStaticReview(nbgl_layoutTagValueList_t *tagValueList, - const nbgl_icon_details_t *icon, - const char *reviewTitle, - const char *acceptText, - const char *rejectText, - nbgl_choiceCallback_t callback) +void nbgl_useCaseStaticReview(nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *acceptText, + const char *rejectText, + nbgl_choiceCallback_t callback) { // memorize context memset(&context, 0, sizeof(UseCaseContext_t)); context.review.forwardNavOnly = false; context.type = REVIEW_USE_CASE; - memcpy(&context.review.tagValueList, tagValueList, sizeof(nbgl_layoutTagValueList_t)); + memcpy(&context.review.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t)); context.review.reviewTitle = reviewTitle; context.review.icon = icon; @@ -460,11 +460,11 @@ void nbgl_useCaseAddressConfirmation(const nbgl_icon_details_t *icon, * @param callback callback called when either confirm or reject page is double pressed * @param tagValueList list of tag/value pairs (must be persistent because no copy) */ -void nbgl_useCaseAddressConfirmationExt(const nbgl_icon_details_t *icon, - const char *title, - const char *address, - nbgl_choiceCallback_t callback, - const nbgl_layoutTagValueList_t *tagValueList) +void nbgl_useCaseAddressConfirmationExt(const nbgl_icon_details_t *icon, + const char *title, + const char *address, + nbgl_choiceCallback_t callback, + const nbgl_contentTagValueList_t *tagValueList) { // memorize context memset(&context, 0, sizeof(UseCaseContext_t)); @@ -472,7 +472,7 @@ void nbgl_useCaseAddressConfirmationExt(const nbgl_icon_details_t *icon, context.type = REVIEW_USE_CASE; if (tagValueList) { - memcpy(&context.review.tagValueList, tagValueList, sizeof(nbgl_layoutTagValueList_t)); + memcpy(&context.review.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t)); } context.review.address = address; diff --git a/lib_standard_app/io.c b/lib_standard_app/io.c index 27581c4fb..cf878725f 100644 --- a/lib_standard_app/io.c +++ b/lib_standard_app/io.c @@ -218,3 +218,20 @@ WEAK int io_send_response_buffers(const buffer_t *rdatalist, size_t count, uint1 return ret; } + +#ifdef STANDARD_APP_SYNC_RAPDU +WEAK bool io_recv_and_process_event(void) +{ + int apdu_state = G_io_app.apdu_state; + + os_io_seph_recv_and_process(0); + + // If an APDU was received in previous os_io_seph_recv_and_process call and + // is waiting to be processed, return true + if (apdu_state == APDU_IDLE && G_io_app.apdu_state != APDU_IDLE) { + return true; + } + + return false; +} +#endif diff --git a/lib_ux_sync/include/ux_sync.h b/lib_ux_sync/include/ux_sync.h new file mode 100644 index 000000000..938ffe10e --- /dev/null +++ b/lib_ux_sync/include/ux_sync.h @@ -0,0 +1,71 @@ +#ifdef HAVE_NBGL + +#include "nbgl_use_case.h" + +typedef enum { + UX_SYNC_RET_APPROVED, + UX_SYNC_RET_REJECTED, + UX_SYNC_RET_QUITTED, + UX_SYNC_RET_APDU_RECEIVED, + UX_SYNC_RET_ERROR +} ux_sync_ret_t; + +ux_sync_ret_t ux_sync_homeAndSettings(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + const uint8_t initSettingPage, + const nbgl_genericContents_t *settingContents, + const nbgl_contentInfoList_t *infosList, + const nbgl_homeAction_t *action); + +ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle); + +ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle); + +ux_sync_ret_t ux_sync_addressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle); + +ux_sync_ret_t ux_sync_reviewStatus(nbgl_reviewStatusType_t reviewStatusType); + +ux_sync_ret_t ux_sync_status(const char *message, bool isSuccess); + +ux_sync_ret_t ux_sync_reviewStreamingStart(nbgl_operationType_t operationType, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle); + +ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList); + +ux_sync_ret_t ux_sync_reviewStreamingFinish(const char *finishTitle); + +ux_sync_ret_t ux_sync_genericReview(const nbgl_genericContents_t *contents, const char *rejectText); + +ux_sync_ret_t ux_sync_genericConfiguration(const char *title, + uint8_t initPage, + const nbgl_genericContents_t *contents); + +/* + * This function must be implemented by the caller. + * It must wait for the next seph event and process it except for APDU events. + * It must return: + * - true when an APDU has been received in the processed event + * - false otherwise + * + * Note on C apps using SDK lib_standard_app, this is already provided in io.c by the lib. + */ +extern bool io_recv_and_process_event(void); + +#endif diff --git a/lib_ux_sync/src/ux_sync.c b/lib_ux_sync/src/ux_sync.c new file mode 100644 index 000000000..ffc977e8f --- /dev/null +++ b/lib_ux_sync/src/ux_sync.c @@ -0,0 +1,329 @@ +#ifdef HAVE_NBGL + +#include "ux_sync.h" + +static ux_sync_ret_t g_ret; +static bool g_ended; + +static void choice_callback(bool confirm) +{ + if (confirm) { + g_ret = UX_SYNC_RET_APPROVED; + } + else { + g_ret = UX_SYNC_RET_REJECTED; + } + + g_ended = true; +} + +static void quit_callback(void) +{ + g_ret = UX_SYNC_RET_QUITTED; + g_ended = true; +} + +static void rejected_callback(void) +{ + g_ret = UX_SYNC_RET_REJECTED; + g_ended = true; +} + +static void ux_sync_init(void) +{ + g_ended = false; + g_ret = UX_SYNC_RET_ERROR; +} + +static ux_sync_ret_t ux_sync_wait(bool exitOnApdu) +{ + bool apduReceived; + + while (!g_ended) { + apduReceived = io_recv_and_process_event(); + if (exitOnApdu && apduReceived) { + return UX_SYNC_RET_APDU_RECEIVED; + } + } + + return g_ret; +} + +/** + * @brief Draws the extended version of home page of an app (page on which we land when launching it + * from dashboard) with automatic support of setting display. + * @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 initSettingPage if not INIT_HOME_PAGE, start directly the corresponding setting page + * @param settingContents setting contents to be displayed + * @param infosList infos to be displayed (version, license, developer, ...) + * @param action if not NULL, info used for an action button (on top of "Quit + * App" button/footer) + * + * @return ret code: + * - UX_SYNC_RET_QUITTED + * - UX_SYNC_RET_APDU_RECEIVED + */ +ux_sync_ret_t ux_sync_homeAndSettings(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + const uint8_t initSettingPage, + const nbgl_genericContents_t *settingContents, + const nbgl_contentInfoList_t *infosList, + const nbgl_homeAction_t *action) +{ + ux_sync_init(); + nbgl_useCaseHomeAndSettings(appName, + appIcon, + tagline, + initSettingPage, + settingContents, + infosList, + action, + quit_callback); + return ux_sync_wait(true); +} + +/** + * @brief Draws a flow of pages of a review. A back key is available on top-left of the screen, + * except in first page It is possible to go to next page thanks to "tap to continue". + * @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 + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_review(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle) +{ + ux_sync_init(); + nbgl_useCaseReview(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief Draws a flow of pages of a light review. A back key is available on top-left of the + * screen,except in first page It is possible to go to next page thanks to "tap to continue". + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed, the last page being a short 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 + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewLight(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle) +{ + ux_sync_init(); + nbgl_useCaseReviewLight(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choice_callback); + return ux_sync_wait(false); +} +/** + * @brief Draws a flow of pages of an extended address verification page. + * A back key is available on top-left of the screen, + * except in first page It is possible to go to next page thanks to "tap to continue". + * @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 address address to confirm (NULL terminated string) + * @param additionalTagValueList list of tag/value pairs (can be NULL) (must fit in a single page, + * and be persistent because no copy) + * @param callback callback called when button or footer is touched (if true, button, if false + * footer) + * @param icon icon used on the first review page + * @param reviewTitle string used in the first review page + * @param reviewSubTitle string to set under reviewTitle (can be NULL) + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_addressReview(const char *address, + const nbgl_contentTagValueList_t *additionalTagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle) +{ + ux_sync_init(); + nbgl_useCaseAddressReview( + address, additionalTagValueList, icon, reviewTitle, reviewSubTitle, choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief Draws a transient (3s) status page for the reviewStatusType + * + * @param reviewStatusType type of status to display + * + * @return ret code: + * - UX_SYNC_RET_QUITTED + */ +ux_sync_ret_t ux_sync_reviewStatus(nbgl_reviewStatusType_t reviewStatusType) +{ + ux_sync_init(); + nbgl_useCaseReviewStatus(reviewStatusType, quit_callback); + return ux_sync_wait(false); +} + +/** + * @brief Draws a transient (3s) status page, either of success or failure, with the given message + * + * @param message string to set in middle of page (Upper case for success) + * @param isSuccess if true, message is drawn in a Ledger style (with corners) + * + * @return ret code: + * - UX_SYNC_RET_QUITTED + */ +ux_sync_ret_t ux_sync_status(const char *message, bool isSuccess) +{ + ux_sync_init(); + nbgl_useCaseStatus(message, isSuccess, quit_callback); + return ux_sync_wait(false); +} + +/** + * @brief Start drawing the flow of pages of a review. + * @note This should be followed by calls to nbgl_useCaseReviewStreamingContinue and finally to + * nbgl_useCaseReviewStreamingFinish. + * + * @param operationType type of operation (Operation, Transaction, Message) + * @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) + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewStreamingStart(nbgl_operationType_t operationType, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle) + +{ + ux_sync_init(); + nbgl_useCaseReviewStreamingStart( + operationType, icon, reviewTitle, reviewSubTitle, choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief Continue drawing the flow of pages of a review. + * @note This should be called after a call to nbgl_useCaseReviewStreamingStart and can be followed + * by others calls to nbgl_useCaseReviewStreamingContinue and finally to + * nbgl_useCaseReviewStreamingFinish. + * + * @param tagValueList list of tag/value pairs + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList) + +{ + ux_sync_init(); + nbgl_useCaseReviewStreamingContinue(tagValueList, choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief finish drawing the flow of pages of a review. + * @note This should be called after a call to nbgl_useCaseReviewStreamingContinue. + * + * @param finishTitle string used in the last review page + * + * @return ret code: + * - UX_SYNC_RET_APPROVED + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_reviewStreamingFinish(const char *finishTitle) + +{ + ux_sync_init(); + nbgl_useCaseReviewStreamingFinish(finishTitle, choice_callback); + return ux_sync_wait(false); +} + +/** + * @brief Draws a flow of pages of a review with automatic pagination depending on content + * to be displayed that is passed through contents. + * + * @param contents contents to be displayed + * @param rejectText text to use in footer + * + * @return ret code: + * - UX_SYNC_RET_REJECTED + */ +ux_sync_ret_t ux_sync_genericReview(const nbgl_genericContents_t *contents, const char *rejectText) + +{ + ux_sync_init(); + nbgl_useCaseGenericReview(contents, rejectText, rejected_callback); + return ux_sync_wait(false); +} + +/** + * @brief Draws a set of pages with automatic pagination depending on content + * to be displayed that is passed through contents. + * + * @param title string to use as title + * @param initPage page on which to start, can be != 0 if you want to display a specific page + * after a confirmation change or something. Then the value should be taken from the + * nbgl_contentActionCallback_t callback call. + * @param contents contents to be displayed + * + * @return ret code: + * - UX_SYNC_RET_QUITTED + * - UX_SYNC_RET_APDU_RECEIVED + */ +ux_sync_ret_t ux_sync_genericConfiguration(const char *title, + uint8_t initPage, + const nbgl_genericContents_t *contents) + +{ + ux_sync_init(); + nbgl_useCaseGenericConfiguration(title, initPage, contents, quit_callback); + return ux_sync_wait(true); +} + +#endif