|
37 | 37 | (pagesData[pageIdx / PAGES_PER_UINT8] = pageData \
|
38 | 38 | << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS))
|
39 | 39 |
|
40 |
| -#define MAX_PAGE_NB 256 |
| 40 | +#define MAX_PAGE_NB 256 |
| 41 | +#define MAX_MODAL_PAGE_NB 32 |
41 | 42 |
|
42 | 43 | /* Alias to clarify usage of genericContext hasStartingContent and hasFinishingContent feature */
|
43 | 44 | #define STARTING_CONTENT localContentsList[0]
|
|
80 | 81 | NEXT_TOKEN,
|
81 | 82 | QUIT_TOKEN,
|
82 | 83 | NAV_TOKEN,
|
| 84 | + MODAL_NAV_TOKEN, |
83 | 85 | SKIP_TOKEN,
|
84 | 86 | CONTINUE_TOKEN,
|
85 | 87 | ADDRESS_QRCODE_BUTTON_TOKEN,
|
@@ -112,6 +114,7 @@ typedef enum {
|
112 | 114 | typedef struct DetailsContext_s {
|
113 | 115 | uint8_t nbPages;
|
114 | 116 | uint8_t currentPage;
|
| 117 | + uint8_t currentPairIdx; |
115 | 118 | bool wrapping;
|
116 | 119 | const char *tag;
|
117 | 120 | const char *value;
|
@@ -184,7 +187,8 @@ typedef struct {
|
184 | 187 | currentCallback; // to be used to retrieve the pairs with value alias
|
185 | 188 | nbgl_layout_t modalLayout;
|
186 | 189 | nbgl_layout_t backgroundLayout;
|
187 |
| - const nbgl_contentInfoList_t *currentInfos; |
| 190 | + const nbgl_contentInfoList_t *currentInfos; |
| 191 | + const nbgl_contentTagValueList_t *currentTagValues; |
188 | 192 | } GenericContext_t;
|
189 | 193 |
|
190 | 194 | typedef struct {
|
@@ -270,6 +274,7 @@ static GenericContext_t genericContext;
|
270 | 274 | static nbgl_content_t
|
271 | 275 | localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
|
272 | 276 | static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
|
| 277 | +static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8]; |
273 | 278 |
|
274 | 279 | // contexts for bundle navigation
|
275 | 280 | static nbgl_BundleNavContext_t bundleNavContext;
|
@@ -334,12 +339,14 @@ static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
|
334 | 339 | **********************/
|
335 | 340 | static void displayReviewPage(uint8_t page, bool forceFullRefresh);
|
336 | 341 | static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
|
| 342 | +static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh); |
337 | 343 | static void displayFullValuePage(const char *backText,
|
338 | 344 | const char *aliasText,
|
339 | 345 | const nbgl_contentValueExt_t *extension);
|
340 | 346 | static void displayInfosListModal(const char *modalTitle,
|
341 | 347 | const nbgl_contentInfoList_t *infos,
|
342 | 348 | bool fromReview);
|
| 349 | +static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues); |
343 | 350 | static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
|
344 | 351 | static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
|
345 | 352 | static void pageCallback(int token, uint8_t index);
|
@@ -421,6 +428,27 @@ static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool
|
421 | 428 | }
|
422 | 429 | }
|
423 | 430 |
|
| 431 | +// Helper to set modalContext page info |
| 432 | +static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements) |
| 433 | +{ |
| 434 | + uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements); |
| 435 | + |
| 436 | + modalContextPagesInfo[pageIdx / PAGES_PER_UINT8] |
| 437 | + &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS)); |
| 438 | + modalContextPagesInfo[pageIdx / PAGES_PER_UINT8] |
| 439 | + |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS); |
| 440 | +} |
| 441 | + |
| 442 | +// Helper to get modalContext page info |
| 443 | +static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements) |
| 444 | +{ |
| 445 | + uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8] |
| 446 | + >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS); |
| 447 | + if (nbElements != NULL) { |
| 448 | + *nbElements = GET_PAGE_NB_ELEMENTS(pageData); |
| 449 | + } |
| 450 | +} |
| 451 | + |
424 | 452 | // Simple helper to get the number of elements inside a nbgl_content_t
|
425 | 453 | static uint8_t getContentNbElement(const nbgl_content_t *content)
|
426 | 454 | {
|
@@ -578,6 +606,16 @@ static void pageModalCallback(int token, uint8_t index)
|
578 | 606 | displayDetailsPage(index, false);
|
579 | 607 | }
|
580 | 608 | }
|
| 609 | + if (token == MODAL_NAV_TOKEN) { |
| 610 | + if (index == EXIT_PAGE) { |
| 611 | + // redraw the background layer |
| 612 | + nbgl_screenRedraw(); |
| 613 | + nbgl_refresh(); |
| 614 | + } |
| 615 | + else { |
| 616 | + displayTagValueListModalPage(index, false); |
| 617 | + } |
| 618 | + } |
581 | 619 | else if (token == QUIT_TOKEN) {
|
582 | 620 | // redraw the background layer
|
583 | 621 | nbgl_screenRedraw();
|
@@ -1071,28 +1109,36 @@ static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
|
1071 | 1109 |
|
1072 | 1110 | break;
|
1073 | 1111 | }
|
1074 |
| - case TAG_VALUE_CONFIRM: |
| 1112 | + case TAG_VALUE_CONFIRM: { |
| 1113 | + nbgl_contentTagValueList_t *p_tagValueList; |
1075 | 1114 | // only display a TAG_VALUE_CONFIRM if we are at the last page
|
1076 | 1115 | if ((nextElementIdx + nbElementsInPage)
|
1077 | 1116 | == p_content->content.tagValueConfirm.tagValueList.nbPairs) {
|
1078 | 1117 | memcpy(&pageContent->tagValueConfirm,
|
1079 | 1118 | &p_content->content.tagValueConfirm,
|
1080 | 1119 | sizeof(pageContent->tagValueConfirm));
|
1081 |
| - nbgl_contentTagValueList_t *p_tagValueList |
1082 |
| - = &pageContent->tagValueConfirm.tagValueList; |
1083 |
| - p_tagValueList->nbPairs = nbElementsInPage; |
1084 |
| - p_tagValueList->pairs |
1085 |
| - = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]); |
| 1120 | + p_tagValueList = &pageContent->tagValueConfirm.tagValueList; |
1086 | 1121 | }
|
1087 | 1122 | else {
|
1088 | 1123 | // else display it as a TAG_VALUE_LIST
|
1089 |
| - pageContent->type = TAG_VALUE_LIST; |
1090 |
| - nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList; |
1091 |
| - p_tagValueList->nbPairs = nbElementsInPage; |
1092 |
| - p_tagValueList->pairs |
1093 |
| - = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]); |
| 1124 | + pageContent->type = TAG_VALUE_LIST; |
| 1125 | + p_tagValueList = &pageContent->tagValueList; |
| 1126 | + } |
| 1127 | + p_tagValueList->nbPairs = nbElementsInPage; |
| 1128 | + p_tagValueList->pairs |
| 1129 | + = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]); |
| 1130 | + // parse pairs to check if any contains an alias for value |
| 1131 | + for (uint8_t i = 0; i < nbElementsInPage; i++) { |
| 1132 | + if (p_tagValueList->pairs[i].aliasValue) { |
| 1133 | + p_tagValueList->token = VALUE_ALIAS_TOKEN; |
| 1134 | + break; |
| 1135 | + } |
1094 | 1136 | }
|
| 1137 | + // memorize pairs (or callback) for usage when alias is used |
| 1138 | + genericContext.currentPairs = p_tagValueList->pairs; |
| 1139 | + genericContext.currentCallback = p_tagValueList->callback; |
1095 | 1140 | break;
|
| 1141 | + } |
1096 | 1142 | case SWITCHES_LIST:
|
1097 | 1143 | pageContent->switchesList.nbSwitches = nbElementsInPage;
|
1098 | 1144 | pageContent->switchesList.switches
|
@@ -1331,6 +1377,10 @@ static void displayFullValuePage(const char *backText,
|
1331 | 1377 | genericContext.currentInfos = extension->infolist;
|
1332 | 1378 | displayInfosListModal(modalTitle, extension->infolist, true);
|
1333 | 1379 | }
|
| 1380 | + else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) { |
| 1381 | + genericContext.currentTagValues = extension->tagValuelist; |
| 1382 | + displayTagValueListModal(extension->tagValuelist); |
| 1383 | + } |
1334 | 1384 | else {
|
1335 | 1385 | nbgl_layoutDescription_t layoutDescription = {.modal = true,
|
1336 | 1386 | .withLeftBorder = true,
|
@@ -1414,6 +1464,60 @@ static void displayInfosListModal(const char *modalTitle,
|
1414 | 1464 | nbgl_refreshSpecial(FULL_COLOR_CLEAN_REFRESH);
|
1415 | 1465 | }
|
1416 | 1466 |
|
| 1467 | +// function used to display the modal containing alias tag-value pairs |
| 1468 | +static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh) |
| 1469 | +{ |
| 1470 | + nbgl_pageNavigationInfo_t info = {.activePage = pageIdx, |
| 1471 | + .nbPages = detailsContext.nbPages, |
| 1472 | + .navType = NAV_WITH_BUTTONS, |
| 1473 | + .quitToken = QUIT_TOKEN, |
| 1474 | + .navWithButtons.navToken = MODAL_NAV_TOKEN, |
| 1475 | + .navWithButtons.quitButton = true, |
| 1476 | + .navWithButtons.backButton = true, |
| 1477 | + .navWithButtons.quitText = NULL, |
| 1478 | + .progressIndicator = false, |
| 1479 | + .tuneId = TUNE_TAP_CASUAL}; |
| 1480 | + nbgl_pageContent_t content = {.type = TAG_VALUE_LIST, |
| 1481 | + .topRightIcon = NULL, |
| 1482 | + .tagValueList.smallCaseForValue = true, |
| 1483 | + .tagValueList.wrapping = detailsContext.wrapping}; |
| 1484 | + uint8_t nbElementsInPage; |
| 1485 | + |
| 1486 | + // if first page or forward |
| 1487 | + if (detailsContext.currentPage <= pageIdx) { |
| 1488 | + modalContextGetPageInfo(pageIdx, &nbElementsInPage); |
| 1489 | + } |
| 1490 | + else { |
| 1491 | + // backward direction |
| 1492 | + modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage); |
| 1493 | + detailsContext.currentPairIdx -= nbElementsInPage; |
| 1494 | + modalContextGetPageInfo(pageIdx, &nbElementsInPage); |
| 1495 | + detailsContext.currentPairIdx -= nbElementsInPage; |
| 1496 | + } |
| 1497 | + detailsContext.currentPage = pageIdx; |
| 1498 | + |
| 1499 | + content.tagValueList.pairs |
| 1500 | + = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx]; |
| 1501 | + content.tagValueList.nbPairs = nbElementsInPage; |
| 1502 | + detailsContext.currentPairIdx += nbElementsInPage; |
| 1503 | + if (info.nbPages == 1) { |
| 1504 | + // if only one page, no navigation bar, and use a footer instead |
| 1505 | + info.navWithButtons.quitText = "Close"; |
| 1506 | + } |
| 1507 | + |
| 1508 | + if (modalPageContext != NULL) { |
| 1509 | + nbgl_pageRelease(modalPageContext); |
| 1510 | + } |
| 1511 | + modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true); |
| 1512 | + |
| 1513 | + if (forceFullRefresh) { |
| 1514 | + nbgl_refreshSpecial(FULL_COLOR_CLEAN_REFRESH); |
| 1515 | + } |
| 1516 | + else { |
| 1517 | + nbgl_refreshSpecial(FULL_COLOR_PARTIAL_REFRESH); |
| 1518 | + } |
| 1519 | +} |
| 1520 | + |
1417 | 1521 | #ifdef NBGL_QRCODE
|
1418 | 1522 | static void displayAddressQRCode(void)
|
1419 | 1523 | {
|
@@ -1861,6 +1965,58 @@ static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
|
1861 | 1965 | return nbPairsInPage;
|
1862 | 1966 | }
|
1863 | 1967 |
|
| 1968 | +/** |
| 1969 | + * @brief computes the number of tag/values pairs displayable in a details page, with the given list |
| 1970 | + * of tag/value pairs |
| 1971 | + * |
| 1972 | + * @param nbPairs number of tag/value pairs to use in \b tagValueList |
| 1973 | + * @param tagValueList list of tag/value pairs |
| 1974 | + * @param startIndex first index to consider in \b tagValueList |
| 1975 | + * @return the number of tag/value pairs fitting in a page |
| 1976 | + */ |
| 1977 | +static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs, |
| 1978 | + const nbgl_contentTagValueList_t *tagValueList, |
| 1979 | + uint8_t startIndex) |
| 1980 | +{ |
| 1981 | + uint8_t nbPairsInPage = 0; |
| 1982 | + uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin |
| 1983 | + uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT; |
| 1984 | + |
| 1985 | + while (nbPairsInPage < nbPairs) { |
| 1986 | + const nbgl_layoutTagValue_t *pair; |
| 1987 | + |
| 1988 | + // margin between pairs |
| 1989 | + // 12 or 24 px between each tag/value pair |
| 1990 | + if (nbPairsInPage > 0) { |
| 1991 | + currentHeight += INTER_TAG_VALUE_MARGIN; |
| 1992 | + } |
| 1993 | + // fetch tag/value pair strings. |
| 1994 | + if (tagValueList->pairs != NULL) { |
| 1995 | + pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]); |
| 1996 | + } |
| 1997 | + else { |
| 1998 | + pair = PIC(tagValueList->callback(startIndex + nbPairsInPage)); |
| 1999 | + } |
| 2000 | + |
| 2001 | + // tag height |
| 2002 | + currentHeight += nbgl_getTextHeightInWidth( |
| 2003 | + SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping); |
| 2004 | + // space between tag and value |
| 2005 | + currentHeight += 4; |
| 2006 | + |
| 2007 | + // value height |
| 2008 | + currentHeight += nbgl_getTextHeightInWidth( |
| 2009 | + SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping); |
| 2010 | + |
| 2011 | + // we have reached the maximum height, it means than there are to many pairs |
| 2012 | + if (currentHeight >= maxUsableHeight) { |
| 2013 | + break; |
| 2014 | + } |
| 2015 | + nbPairsInPage++; |
| 2016 | + } |
| 2017 | + return nbPairsInPage; |
| 2018 | +} |
| 2019 | + |
1864 | 2020 | static uint8_t getNbPagesForContent(const nbgl_content_t *content,
|
1865 | 2021 | uint8_t pageIdxStart,
|
1866 | 2022 | bool isLast,
|
@@ -2729,6 +2885,29 @@ static void displayDetails(const char *tag, const char *value, bool wrapping)
|
2729 | 2885 | displayDetailsPage(0, true);
|
2730 | 2886 | }
|
2731 | 2887 |
|
| 2888 | +// function used to display the modal containing alias tag-value pairs |
| 2889 | +static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues) |
| 2890 | +{ |
| 2891 | + uint8_t nbElements = 0; |
| 2892 | + uint8_t nbElementsInPage; |
| 2893 | + uint8_t elemIdx = 0; |
| 2894 | + |
| 2895 | + // initialize context |
| 2896 | + memset(&detailsContext, 0, sizeof(detailsContext)); |
| 2897 | + nbElements = tagValues->nbPairs; |
| 2898 | + |
| 2899 | + while (nbElements > 0) { |
| 2900 | + nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx); |
| 2901 | + |
| 2902 | + elemIdx += nbElementsInPage; |
| 2903 | + modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage); |
| 2904 | + nbElements -= nbElementsInPage; |
| 2905 | + detailsContext.nbPages++; |
| 2906 | + } |
| 2907 | + |
| 2908 | + displayTagValueListModalPage(0, true); |
| 2909 | +} |
| 2910 | + |
2732 | 2911 | /**********************
|
2733 | 2912 | * GLOBAL FUNCTIONS
|
2734 | 2913 | **********************/
|
|
0 commit comments