From 306ee929208c0bf61cab91b68043306082c9ecd3 Mon Sep 17 00:00:00 2001 From: Charles-Edouard de la Vergne Date: Fri, 3 Jan 2025 16:14:33 +0100 Subject: [PATCH] Add missing API or feature for Nano on NBGL --- lib_nbgl/src/nbgl_use_case.c | 2 + lib_nbgl/src/nbgl_use_case_nanos.c | 258 ++++++++++++++++++++++++----- 2 files changed, 214 insertions(+), 46 deletions(-) diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index 79eb6a636..917100787 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -10,6 +10,7 @@ *********************/ #include #include +#include "nbgl_page.h" #include "nbgl_debug.h" #include "nbgl_use_case.h" #include "glyphs.h" @@ -3056,6 +3057,7 @@ void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationT blindSigningWarning(); } + /** * @brief Draws a flow of pages of a light review. Navigation operates with either swipe or * navigation keys at bottom right. The last page contains a button/footer with the given diff --git a/lib_nbgl/src/nbgl_use_case_nanos.c b/lib_nbgl/src/nbgl_use_case_nanos.c index 0b821c461..43c41a4ba 100644 --- a/lib_nbgl/src/nbgl_use_case_nanos.c +++ b/lib_nbgl/src/nbgl_use_case_nanos.c @@ -30,6 +30,7 @@ typedef struct ReviewContext_s { const nbgl_contentTagValueList_t *tagValueList; const nbgl_icon_details_t *icon; const char *reviewTitle; + const char *reviewSubTitle; const char *address; // for address confirmation review } ReviewContext_t; @@ -54,6 +55,7 @@ typedef struct HomeContext_s { typedef enum { NONE_USE_CASE, REVIEW_USE_CASE, + REVIEW_BLIND_SIGN_USE_CASE, ADDRESS_REVIEW_USE_CASE, STREAMING_START_REVIEW_USE_CASE, STREAMING_CONTINUE_REVIEW_USE_CASE, @@ -376,17 +378,24 @@ static void statusTickerCallback(void) // function used to display the current page in review static void displayReviewPage(nbgl_stepPosition_t pos) { - const char *text = NULL; - const char *subText = NULL; - const nbgl_icon_details_t *icon = NULL; + uint8_t reviewPage = 0; + uint8_t pairIndex = 0; + const char *text = NULL; + const char *subText = NULL; + const nbgl_icon_details_t *icon = NULL; context.stepCallback = NULL; - if (context.currentPage == 0) { // title page - icon = context.review.icon; - text = context.review.reviewTitle; + // Determine the 1st page to display tag/values + reviewPage = 1; // 1st page is for title + if (context.type == REVIEW_BLIND_SIGN_USE_CASE) { + reviewPage++; // 2nd page is for warning + } + if (context.review.reviewSubTitle) { + reviewPage++; // 3rd page is for subtitle } - else if (context.currentPage == (context.nbPages - 2)) { // accept page + // Determine which page to display + if (context.currentPage == (context.nbPages - 2)) { // accept page icon = &C_icon_validate_14; text = "Approve"; context.stepCallback = onReviewAccept; @@ -396,14 +405,50 @@ static void displayReviewPage(nbgl_stepPosition_t pos) text = "Reject"; context.stepCallback = onReviewReject; } - else if ((context.review.address != NULL) - && (context.currentPage == 1)) { // address confirmation and 2nd page + else if (context.currentPage < reviewPage) { + // header page(s) + switch (context.currentPage) { + case 0: + if (context.type == REVIEW_BLIND_SIGN_USE_CASE) { + // warning page + icon = &C_icon_warning; + text = "Blind\nsigning"; + } + else { + // title page + icon = context.review.icon; + text = context.review.reviewTitle; + } + break; + case 1: + if (context.type == REVIEW_BLIND_SIGN_USE_CASE) { + // title page + icon = context.review.icon; + text = context.review.reviewTitle; + } + else { + // subtitle page + text = context.review.reviewSubTitle; + } + break; + case 2: + // subtitle page + text = context.review.reviewSubTitle; + break; + default: + break; + } + } + else if ((context.review.address != NULL) && (context.currentPage == reviewPage)) { + // address confirmation and 2nd page text = "Address"; subText = context.review.address; } else { - uint8_t pairIndex = (context.review.address != NULL) ? (context.currentPage - 2) - : (context.currentPage - 1); + pairIndex = context.currentPage - reviewPage; + if (context.review.address != NULL) { + pairIndex--; + } getPairData(context.review.tagValueList, pairIndex, &text, &subText); } @@ -414,16 +459,35 @@ static void displayReviewPage(nbgl_stepPosition_t pos) // function used to display the current page in review static void displayStreamingReviewPage(nbgl_stepPosition_t pos) { - const char *text = NULL; - const char *subText = NULL; - const nbgl_icon_details_t *icon = NULL; + uint8_t reviewPage = 0; + const char *text = NULL; + const char *subText = NULL; + const nbgl_icon_details_t *icon = NULL; context.stepCallback = NULL; if (context.type == STREAMING_START_REVIEW_USE_CASE) { - if (context.currentPage == 0) { // title page - icon = context.review.icon; - text = context.review.reviewTitle; + // Determine the 1st page to display tag/values + reviewPage = 1; // 1st page is for title + if (context.review.reviewSubTitle) { + reviewPage++; // 3rd page is for subtitle + } + // Determine which page to display + if (context.currentPage < reviewPage) { + // header page(s) + switch (context.currentPage) { + case 0: + // title page + icon = context.review.icon; + text = context.review.reviewTitle; + break; + case 1: + // subtitle page + text = context.review.reviewSubTitle; + break; + default: + break; + } } else { nbgl_useCaseSpinner("Processing"); @@ -704,6 +768,37 @@ static void displayChoicePage(nbgl_stepPosition_t pos) nbgl_refresh(); } +// function to factorize code for all simple reviews +static void useCaseReview(ContextType_t type, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + nbgl_choiceCallback_t choiceCallback) +{ + UNUSED(finishTitle); // TODO dedicated screen for it? + + memset(&context, 0, sizeof(UseCaseContext_t)); + context.type = type; + context.review.tagValueList = tagValueList; + context.review.reviewTitle = reviewTitle; + context.review.reviewSubTitle = reviewSubTitle; + context.review.icon = icon; + context.review.onChoice = choiceCallback; + context.currentPage = 0; + // + 3 because 1 page for title and 2 pages at the end for accept/reject + context.nbPages = tagValueList->nbPairs + 3; + if (type == REVIEW_BLIND_SIGN_USE_CASE) { + context.nbPages++; // 1 page for warning + } + if (reviewSubTitle) { + context.nbPages++; // 1 page for subtitle page + } + + displayReviewPage(FORWARD_DIRECTION); +} + /********************** * GLOBAL FUNCTIONS **********************/ @@ -771,21 +866,93 @@ void nbgl_useCaseReview(nbgl_operationType_t operationType, const char *finishTitle, nbgl_choiceCallback_t choiceCallback) { - UNUSED(operationType); // TODO adapt accept and reject text depending on this value? - UNUSED(reviewSubTitle); // TODO dedicated screen for it? - UNUSED(finishTitle); // TODO dedicated screen for it? + UNUSED(operationType); // TODO adapt accept and reject text depending on this value? + + useCaseReview(REVIEW_USE_CASE, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choiceCallback); +} - memset(&context, 0, sizeof(UseCaseContext_t)); - context.type = REVIEW_USE_CASE; - context.review.tagValueList = tagValueList; - context.review.reviewTitle = reviewTitle; - context.review.icon = icon; - context.review.onChoice = choiceCallback; - context.currentPage = 0; - // + 3 because 1 page for title and 2 pages at the end for accept/reject - context.nbPages = tagValueList->nbPairs + 3; +/** + * @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 dummy inconsistent parameter on Nano devices (ignored) + * @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 *dummy, + nbgl_choiceCallback_t choiceCallback) +{ + UNUSED(operationType); // TODO adapt accept and reject text depending on this value? + UNUSED(dummy); + + useCaseReview(REVIEW_USE_CASE, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choiceCallback); +} - displayReviewPage(FORWARD_DIRECTION); +/** + * @brief Draws a flow of pages of a blind-signing review. The review is preceded by a warning page + * + * 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 dummy inconsistent parameter on Nano devices (ignored) + * @param choiceCallback callback called when operation is accepted (param is true) or rejected + * (param is false) + */ +void nbgl_useCaseReviewBlindSigning(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 *dummy, + nbgl_choiceCallback_t choiceCallback) +{ + UNUSED(operationType); // TODO adapt accept and reject text depending on this value? + UNUSED(dummy); + + useCaseReview(REVIEW_BLIND_SIGN_USE_CASE, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + choiceCallback); } /** @@ -842,15 +1009,14 @@ void nbgl_useCaseAddressReview(const char *address, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback) { - UNUSED(reviewSubTitle); // TODO dedicated screen for it? - memset(&context, 0, sizeof(UseCaseContext_t)); - context.type = ADDRESS_REVIEW_USE_CASE; - context.review.address = address; - context.review.reviewTitle = reviewTitle; - context.review.icon = icon; - context.review.onChoice = choiceCallback; - context.currentPage = 0; + context.type = ADDRESS_REVIEW_USE_CASE; + context.review.address = address; + context.review.reviewTitle = reviewTitle; + context.review.reviewSubTitle = reviewSubTitle; + context.review.icon = icon; + context.review.onChoice = choiceCallback; + context.currentPage = 0; // + 4 because 1 page for title, 1 for address and 2 pages at the end for approve/reject context.nbPages = 4; if (additionalTagValueList) { @@ -958,16 +1124,16 @@ void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback) { - UNUSED(operationType); // TODO adapt accept and reject text depending on this value? - UNUSED(reviewSubTitle); // TODO dedicated screen for it? + UNUSED(operationType); // TODO adapt accept and reject text depending on this value? memset(&context, 0, sizeof(UseCaseContext_t)); - context.type = STREAMING_START_REVIEW_USE_CASE; - context.review.reviewTitle = reviewTitle; - context.review.icon = icon; - context.review.onChoice = choiceCallback; - context.currentPage = 0; - context.nbPages = 1 + 1; // Start page + trick for review continue + context.type = STREAMING_START_REVIEW_USE_CASE; + context.review.reviewTitle = reviewTitle; + context.review.reviewSubTitle = reviewSubTitle; + context.review.icon = icon; + context.review.onChoice = choiceCallback; + context.currentPage = 0; + context.nbPages = 1 + 1; // Start page + trick for review continue displayStreamingReviewPage(FORWARD_DIRECTION); }