Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
nbgl_use_case.c
Go to the documentation of this file.
1
6#ifdef NBGL_USE_CASE
7#ifdef HAVE_SE_TOUCH
8/*********************
9 * INCLUDES
10 *********************/
11#include <string.h>
12#include <stdio.h>
13#include "nbgl_debug.h"
14#include "nbgl_use_case.h"
15#include "glyphs.h"
16#include "os_io_seph_ux.h"
17#include "os_pic.h"
18#include "os_print.h"
19#include "os_helpers.h"
20
21/*********************
22 * DEFINES
23 *********************/
24
25/* Defines for definition and usage of genericContextPagesInfo */
26#define PAGE_NB_ELEMENTS_BITS 3
27#define GET_PAGE_NB_ELEMENTS(pageData) ((pageData) &0x07)
28#define SET_PAGE_NB_ELEMENTS(nbElements) ((nbElements) &0x07)
29
30#define PAGE_FLAG_BITS 1
31#define GET_PAGE_FLAG(pageData) (((pageData) &0x08) >> 3)
32#define SET_PAGE_FLAG(flag) (((flag) &0x01) << 3)
33
34#define PAGE_DATA_BITS (PAGE_NB_ELEMENTS_BITS + PAGE_FLAG_BITS)
35#define PAGES_PER_UINT8 (8 / PAGE_DATA_BITS)
36#define SET_PAGE_DATA(pageIdx, pageData) \
37 (pagesData[pageIdx / PAGES_PER_UINT8] = pageData \
38 << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS))
39
40#define MAX_PAGE_NB 256
41#define MAX_MODAL_PAGE_NB 32
42
43/* Alias to clarify usage of genericContext hasStartingContent and hasFinishingContent feature */
44#define STARTING_CONTENT localContentsList[0]
45#define FINISHING_CONTENT localContentsList[1]
46
47/* max number of lines for address under QR Code */
48#define QRCODE_NB_MAX_LINES 3
49/* max number of char for reduced QR Code address */
50#define QRCODE_REDUCED_ADDR_LEN 128
51
52/* maximum number of pairs in a page of address confirm */
53#define ADDR_VERIF_NB_PAIRS 3
54
55// macros to ease access to shared contexts
56#define keypadContext sharedContext.keypad
57#define reviewWithWarnCtx sharedContext.reviewWithWarning
58#define choiceWithDetailsCtx sharedContext.choiceWithDetails
59
60/* max length of the string displaying the description of the Web3 Checks report */
61#define W3C_DESCRIPTION_MAX_LEN 128
62
68#define RISKY_OPERATION (1 << 6)
69
75#define NO_THREAT_OPERATION (1 << 7)
76
77/**********************
78 * TYPEDEFS
79 **********************/
80enum {
81 BACK_TOKEN = 0,
82 NEXT_TOKEN,
83 QUIT_TOKEN,
84 NAV_TOKEN,
85 MODAL_NAV_TOKEN,
86 SKIP_TOKEN,
87 CONTINUE_TOKEN,
88 ADDRESS_QRCODE_BUTTON_TOKEN,
89 ACTION_BUTTON_TOKEN,
90 CHOICE_TOKEN,
91 DETAILS_BUTTON_TOKEN,
92 CONFIRM_TOKEN,
93 REJECT_TOKEN,
94 VALUE_ALIAS_TOKEN,
95 INFO_ALIAS_TOKEN,
96 INFOS_TIP_BOX_TOKEN,
97 BLIND_WARNING_TOKEN,
98 WARNING_BUTTON_TOKEN,
99 CHOICE_DETAILS_TOKEN,
100 TIP_BOX_TOKEN,
101 QUIT_TIPBOX_MODAL_TOKEN,
102 WARNING_CHOICE_TOKEN,
103 DISMISS_QR_TOKEN,
104 DISMISS_WARNING_TOKEN,
105 DISMISS_DETAILS_TOKEN,
106 FIRST_WARN_BAR_TOKEN,
107 LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
108};
109
110typedef enum {
111 REVIEW_NAV = 0,
112 SETTINGS_NAV,
113 GENERIC_NAV,
114 STREAMING_NAV
115} NavType_t;
116
117typedef struct DetailsContext_s {
118 uint8_t nbPages;
119 uint8_t currentPage;
120 uint8_t currentPairIdx;
121 bool wrapping;
122 const char *tag;
123 const char *value;
124 const char *nextPageStart;
125} DetailsContext_t;
126
127typedef struct AddressConfirmationContext_s {
128 nbgl_layoutTagValue_t tagValuePairs[ADDR_VERIF_NB_PAIRS];
129 nbgl_layout_t *modalLayout;
130 uint8_t nbPairs;
131} AddressConfirmationContext_t;
132
133#ifdef NBGL_KEYPAD
134typedef struct KeypadContext_s {
135 uint8_t pinEntry[KEYPAD_MAX_DIGITS];
136 uint8_t pinLen;
137 uint8_t pinMinDigits;
138 uint8_t pinMaxDigits;
139 nbgl_layout_t *layoutCtx;
140 bool hidden;
141} KeypadContext_t;
142#endif
143
144typedef struct ReviewWithWarningContext_s {
145 bool isStreaming;
146 nbgl_operationType_t operationType;
147 const nbgl_contentTagValueList_t *tagValueList;
148 const nbgl_icon_details_t *icon;
149 const char *reviewTitle;
150 const char *reviewSubTitle;
151 const char *finishTitle;
152 const nbgl_warning_t *warning;
153 nbgl_choiceCallback_t choiceCallback;
154 nbgl_layout_t *layoutCtx;
155 nbgl_layout_t *modalLayout;
156 uint8_t securityReportLevel; // level 1 is the first level of menus
157 bool isIntro; // set to true during intro (before actual review)
158} ReviewWithWarningContext_t;
159
160typedef struct ChoiceWithDetailsContext_s {
161 const nbgl_genericDetails_t *details;
162 nbgl_layout_t *layoutCtx;
163 nbgl_layout_t *modalLayout;
164 uint8_t level; // level 1 is the first level of menus
165} ChoiceWithDetailsContext_t;
166
167typedef enum {
168 SHARE_CTX_KEYPAD,
169 SHARE_CTX_REVIEW_WITH_WARNING,
170 SHARE_CTX_CHOICE_WITH_DETAILS,
171} SharedContextUsage_t;
172
173// this union is intended to save RAM for context storage
174// indeed, these 3 contexts cannot happen simultaneously
175typedef struct {
176 union {
177#ifdef NBGL_KEYPAD
178 KeypadContext_t keypad;
179#endif
180 ReviewWithWarningContext_t reviewWithWarning;
181 ChoiceWithDetailsContext_t choiceWithDetails;
182 };
183 SharedContextUsage_t usage;
184} SharedContext_t;
185
186typedef enum {
187 USE_CASE_GENERIC = 0,
188 USE_CASE_SPINNER
189} GenericContextType_t;
190
191typedef struct {
192 GenericContextType_t type; // type of Generic context usage
193 uint8_t spinnerPosition;
194 nbgl_genericContents_t genericContents;
195 int8_t currentContentIdx;
196 uint8_t currentContentElementNb;
197 uint8_t currentElementIdx;
198 bool hasStartingContent;
199 bool hasFinishingContent;
200 const char *detailsItem;
201 const char *detailsvalue;
202 bool detailsWrapping;
203 bool validWarningCtx; // set to true if the WarningContext is valid
205 *currentPairs; // to be used to retrieve the pairs with value alias
207 currentCallback; // to be used to retrieve the pairs with value alias
208 nbgl_layout_t modalLayout;
209 nbgl_layout_t backgroundLayout;
210 const nbgl_contentInfoList_t *currentInfos;
211 const nbgl_contentTagValueList_t *currentTagValues;
212} GenericContext_t;
213
214typedef struct {
215 const char *appName;
216 const nbgl_icon_details_t *appIcon;
217 const char *tagline;
218 const nbgl_genericContents_t *settingContents;
219 const nbgl_contentInfoList_t *infosList;
220 nbgl_homeAction_t homeAction;
221 nbgl_callback_t quitCallback;
222} nbgl_homeAndSettingsContext_t;
223
224typedef struct {
225 nbgl_operationType_t operationType;
226 nbgl_choiceCallback_t choiceCallback;
227} nbgl_reviewContext_t;
228
229typedef struct {
230 nbgl_operationType_t operationType;
231 nbgl_choiceCallback_t choiceCallback;
232 nbgl_callback_t skipCallback;
233 const nbgl_icon_details_t *icon;
234 uint8_t stepPageNb;
235} nbgl_reviewStreamingContext_t;
236
237typedef union {
238 nbgl_homeAndSettingsContext_t homeAndSettings;
239 nbgl_reviewContext_t review;
240 nbgl_reviewStreamingContext_t reviewStreaming;
241} nbgl_BundleNavContext_t;
242
243typedef struct {
244 const nbgl_icon_details_t *icon;
245 const char *text;
246 const char *subText;
247} SecurityReportItem_t;
248
249/**********************
250 * STATIC VARIABLES
251 **********************/
252
253// char buffers to build some strings
254static char tmpString[W3C_DESCRIPTION_MAX_LEN];
255
256// multi-purposes callbacks
257static nbgl_callback_t onQuit;
258static nbgl_callback_t onContinue;
259static nbgl_callback_t onAction;
260static nbgl_navCallback_t onNav;
261static nbgl_layoutTouchCallback_t onControls;
262static nbgl_contentActionCallback_t onContentAction;
263static nbgl_choiceCallback_t onChoice;
264static nbgl_callback_t onModalConfirm;
265#ifdef NBGL_KEYPAD
266static nbgl_pinValidCallback_t onValidatePin;
267#endif
268
269// contexts for background and modal pages
270static nbgl_page_t *pageContext;
271static nbgl_page_t *modalPageContext;
272
273// context for pages
274static const char *pageTitle;
275
276// context for tip-box
277static nbgl_tipBox_t activeTipBox;
278
279// context for navigation use case
280static nbgl_pageNavigationInfo_t navInfo;
281static bool forwardNavOnly;
282static NavType_t navType;
283
284static DetailsContext_t detailsContext;
285
286// multi-purpose context shared for non-concurrent usages
287static SharedContext_t sharedContext;
288
289// context for address review
290static AddressConfirmationContext_t addressConfirmationContext;
291
292// contexts for generic navigation
293static GenericContext_t genericContext;
294static nbgl_content_t
295 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
296static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
297static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
298
299// contexts for bundle navigation
300static nbgl_BundleNavContext_t bundleNavContext;
301
302// indexed by nbgl_contentType_t
303static const uint8_t nbMaxElementsPerContentType[] = {
304 1, // CENTERED_INFO
305 1, // EXTENDED_CENTER
306 1, // INFO_LONG_PRESS
307 1, // INFO_BUTTON
308 1, // TAG_VALUE_LIST (computed dynamically)
309 1, // TAG_VALUE_DETAILS
310 1, // TAG_VALUE_CONFIRM
311 3, // SWITCHES_LIST (computed dynamically)
312 3, // INFOS_LIST (computed dynamically)
313 5, // CHOICES_LIST (computed dynamically)
314 5, // BARS_LIST (computed dynamically)
315};
316
317// clang-format off
318static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
320 .icon = &WARNING_ICON,
321 .text = "Blind signing required",
322 .subText = "This transaction's details are not fully verifiable. If "
323 "you sign, you could lose all your assets."
324 },
325 [W3C_ISSUE_WARN] = {
326 .icon = &WARNING_ICON,
327 .text = "Transaction Check unavailable",
328 .subText = NULL
329 },
331 .icon = &WARNING_ICON,
332 .text = "Risk detected",
333 .subText = "This transaction was scanned as risky by Web3 Checks."
334 },
336 .icon = &WARNING_ICON,
337 .text = "Critical threat",
338 .subText = "This transaction was scanned as malicious by Web3 Checks."
339 },
341 .icon = NULL,
342 .text = "No threat detected",
343 .subText = "Transaction Check didn't find any threat, but always "
344 "review transaction details carefully."
345 }
346};
347// clang-format on
348
349// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
350static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
351
352#ifdef NBGL_QRCODE
353/* buffer to store reduced address under QR Code */
354static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
355#endif // NBGL_QRCODE
356
357/**********************
358 * STATIC FUNCTIONS
359 **********************/
360static void displayReviewPage(uint8_t page, bool forceFullRefresh);
361static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
362static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
363static void displayFullValuePage(const char *backText,
364 const char *aliasText,
365 const nbgl_contentValueExt_t *extension);
366static void displayInfosListModal(const char *modalTitle,
367 const nbgl_contentInfoList_t *infos,
368 bool fromReview);
369static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
370static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
371static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
372static void pageCallback(int token, uint8_t index);
373#ifdef NBGL_QRCODE
374static void displayAddressQRCode(void);
375#endif // NBGL_QRCODE
376static void modalLayoutTouchCallback(int token, uint8_t index);
377static void displaySkipWarning(void);
378
379static void bundleNavStartHome(void);
380static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
381static void bundleNavStartSettings(void);
382
383static void bundleNavReviewStreamingChoice(bool confirm);
384static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token);
385static void displaySecurityReport(uint32_t set);
386static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
387static void displayInitialWarning(void);
388static void useCaseReview(nbgl_operationType_t operationType,
389 const nbgl_contentTagValueList_t *tagValueList,
390 const nbgl_icon_details_t *icon,
391 const char *reviewTitle,
392 const char *reviewSubTitle,
393 const char *finishTitle,
394 const nbgl_tipBox_t *tipBox,
395 nbgl_choiceCallback_t choiceCallback,
396 bool isLight,
397 bool playNotifSound);
398static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
399 const nbgl_icon_details_t *icon,
400 const char *reviewTitle,
401 const char *reviewSubTitle,
402 nbgl_choiceCallback_t choiceCallback,
403 bool playNotifSound);
404static void useCaseHomeExt(const char *appName,
405 const nbgl_icon_details_t *appIcon,
406 const char *tagline,
407 bool withSettings,
408 nbgl_homeAction_t *homeAction,
409 nbgl_callback_t topRightCallback,
410 nbgl_callback_t quitCallback);
411static void displayDetails(const char *tag, const char *value, bool wrapping);
412
413static void reset_callbacks(void)
414{
415 onQuit = NULL;
416 onContinue = NULL;
417 onAction = NULL;
418 onNav = NULL;
419 onControls = NULL;
420 onContentAction = NULL;
421 onChoice = NULL;
422 onModalConfirm = NULL;
423#ifdef NBGL_KEYPAD
424 onValidatePin = NULL;
425#endif
426}
427
428// Helper to set genericContext page info
429static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
430{
431 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
432
433 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
434 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
435 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
436 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
437}
438
439// Helper to get genericContext page info
440static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
441{
442 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
443 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
444 if (nbElements != NULL) {
445 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
446 }
447 if (flag != NULL) {
448 *flag = GET_PAGE_FLAG(pageData);
449 }
450}
451
452// Helper to set modalContext page info
453static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
454{
455 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
456
457 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
458 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
459 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
460 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
461}
462
463// Helper to get modalContext page info
464static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
465{
466 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
467 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
468 if (nbElements != NULL) {
469 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
470 }
471}
472
473// Simple helper to get the number of elements inside a nbgl_content_t
474static uint8_t getContentNbElement(const nbgl_content_t *content)
475{
476 switch (content->type) {
477 case TAG_VALUE_LIST:
478 return content->content.tagValueList.nbPairs;
481 case SWITCHES_LIST:
482 return content->content.switchesList.nbSwitches;
483 case INFOS_LIST:
484 return content->content.infosList.nbInfos;
485 case CHOICES_LIST:
486 return content->content.choicesList.nbChoices;
487 case BARS_LIST:
488 return content->content.barsList.nbBars;
489 default:
490 return 1;
491 }
492}
493
494// Helper to retrieve the content inside a nbgl_genericContents_t using
495// either the contentsList or using the contentGetterCallback
496static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
497 int8_t contentIdx,
498 nbgl_content_t *content)
499{
500 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
501 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
502 return NULL;
503 }
504
505 if (genericContents->callbackCallNeeded) {
506 // Retrieve content through callback, but first memset the content.
507 memset(content, 0, sizeof(nbgl_content_t));
508 genericContents->contentGetterCallback(contentIdx, content);
509 return content;
510 }
511 else {
512 // Retrieve content through list
513 return PIC(&genericContents->contentsList[contentIdx]);
514 }
515}
516
517static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
518{
519 memset(&navInfo, 0, sizeof(navInfo));
520
521 navInfo.nbPages = nbPages;
522 navInfo.tuneId = TUNE_TAP_CASUAL;
523 navInfo.progressIndicator = false;
524 navInfo.navType = NAV_WITH_BUTTONS;
525
526 if (isReview == false) {
527 navInfo.navWithButtons.navToken = NAV_TOKEN;
528 navInfo.navWithButtons.backButton = true;
529 }
530 else {
531 navInfo.quitToken = REJECT_TOKEN;
532 navInfo.navWithButtons.quitText = rejectText;
533 navInfo.navWithButtons.navToken = NAV_TOKEN;
535 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
536 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
537 }
538}
539
540static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
541 const nbgl_icon_details_t *icon,
542 const char *reviewTitle,
543 const char *reviewSubTitle)
544{
545 contentCenter->icon = icon;
546 contentCenter->title = reviewTitle;
547 contentCenter->description = reviewSubTitle;
548 contentCenter->subText = "Swipe to review";
549 contentCenter->smallTitle = NULL;
550 contentCenter->iconHug = 0;
551 contentCenter->padding = false;
552 contentCenter->illustrType = ICON_ILLUSTRATION;
553}
554
555static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
556{
557 if (finishTitle != NULL) {
558 return finishTitle;
559 }
560 switch (operationType & REAL_TYPE_MASK) {
561 case TYPE_TRANSACTION:
562 return "Sign transaction";
563 case TYPE_MESSAGE:
564 return "Sign message";
565 default:
566 return "Sign operation";
567 }
568}
569
570static void prepareReviewLastPage(nbgl_operationType_t operationType,
571 nbgl_contentInfoLongPress_t *infoLongPress,
572 const nbgl_icon_details_t *icon,
573 const char *finishTitle)
574{
575 infoLongPress->text = getFinishTitle(operationType, finishTitle);
576 infoLongPress->icon = icon;
577 infoLongPress->longPressText = "Hold to sign";
578 infoLongPress->longPressToken = CONFIRM_TOKEN;
579}
580
581static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
582 nbgl_contentInfoButton_t *infoButton,
583 const nbgl_icon_details_t *icon,
584 const char *finishTitle)
585{
586 infoButton->text = getFinishTitle(operationType, finishTitle);
587 infoButton->icon = icon;
588 infoButton->buttonText = "Approve";
589 infoButton->buttonToken = CONFIRM_TOKEN;
590}
591
592static const char *getRejectReviewText(nbgl_operationType_t operationType)
593{
594 UNUSED(operationType);
595 return "Reject";
596}
597
598// function called when navigating (or exiting) modal details pages
599// or when skip choice is displayed
600static void pageModalCallback(int token, uint8_t index)
601{
602 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
603
604 if (token == INFOS_TIP_BOX_TOKEN) {
605 // the icon next to info type alias has been touched
606 displayFullValuePage(activeTipBox.infos.infoTypes[index],
607 activeTipBox.infos.infoContents[index],
608 &activeTipBox.infos.infoExtensions[index]);
609 return;
610 }
611 else if (token == INFO_ALIAS_TOKEN) {
612 // the icon next to info type alias has been touched, but coming from review
613 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
614 genericContext.currentInfos->infoContents[index],
615 &genericContext.currentInfos->infoExtensions[index]);
616 return;
617 }
618 nbgl_pageRelease(modalPageContext);
619 modalPageContext = NULL;
620 if (token == NAV_TOKEN) {
621 if (index == EXIT_PAGE) {
622 // redraw the background layer
624 nbgl_refresh();
625 }
626 else {
627 displayDetailsPage(index, false);
628 }
629 }
630 if (token == MODAL_NAV_TOKEN) {
631 if (index == EXIT_PAGE) {
632 // redraw the background layer
634 nbgl_refresh();
635 }
636 else {
637 displayTagValueListModalPage(index, false);
638 }
639 }
640 else if (token == QUIT_TOKEN) {
641 // redraw the background layer
643 nbgl_refresh();
644 }
645 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
646 // if in "warning context", go back to security report
647 if (reviewWithWarnCtx.securityReportLevel == 2) {
648 reviewWithWarnCtx.securityReportLevel = 1;
649 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
650 }
651 else {
652 // redraw the background layer
654 nbgl_refresh();
655 }
656 }
657 else if (token == SKIP_TOKEN) {
658 if (index == 0) {
659 // display the last forward only review page, whatever it is
660 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
661 }
662 else {
663 // display background, which should be the page where skip has been touched
665 nbgl_refresh();
666 }
667 }
668 else if (token == CHOICE_TOKEN) {
669 if (index == 0) {
670 if (onModalConfirm != NULL) {
671 onModalConfirm();
672 }
673 }
674 else {
675 // display background, which should be the page where skip has been touched
677 nbgl_refresh();
678 }
679 }
680}
681
682// generic callback for all pages except modal
683static void pageCallback(int token, uint8_t index)
684{
685 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
686 if (token == QUIT_TOKEN) {
687 if (onQuit != NULL) {
688 onQuit();
689 }
690 }
691 else if (token == CONTINUE_TOKEN) {
692 if (onContinue != NULL) {
693 onContinue();
694 }
695 }
696 else if (token == CHOICE_TOKEN) {
697 if (onChoice != NULL) {
698 onChoice((index == 0) ? true : false);
699 }
700 }
701 else if (token == ACTION_BUTTON_TOKEN) {
702 if ((index == 0) && (onAction != NULL)) {
703 onAction();
704 }
705 else if ((index == 1) && (onQuit != NULL)) {
706 onQuit();
707 }
708 }
709#ifdef NBGL_QRCODE
710 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
711 displayAddressQRCode();
712 }
713#endif // NBGL_QRCODE
714 else if (token == CONFIRM_TOKEN) {
715 if (onChoice != NULL) {
716 onChoice(true);
717 }
718 }
719 else if (token == REJECT_TOKEN) {
720 if (onChoice != NULL) {
721 onChoice(false);
722 }
723 }
724 else if (token == DETAILS_BUTTON_TOKEN) {
725 displayDetails(genericContext.detailsItem,
726 genericContext.detailsvalue,
727 genericContext.detailsWrapping);
728 }
729 else if (token == NAV_TOKEN) {
730 if (index == EXIT_PAGE) {
731 if (onQuit != NULL) {
732 onQuit();
733 }
734 }
735 else {
736 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
737 displayGenericContextPage(index, false);
738 }
739 else if (navType == REVIEW_NAV) {
740 displayReviewPage(index, false);
741 }
742 else {
743 displaySettingsPage(index, false);
744 }
745 }
746 }
747 else if (token == NEXT_TOKEN) {
748 if (onNav != NULL) {
749 displayReviewPage(navInfo.activePage + 1, false);
750 }
751 else {
752 displayGenericContextPage(navInfo.activePage + 1, false);
753 }
754 }
755 else if (token == BACK_TOKEN) {
756 if (onNav != NULL) {
757 displayReviewPage(navInfo.activePage - 1, true);
758 }
759 else {
760 displayGenericContextPage(navInfo.activePage - 1, false);
761 }
762 }
763 else if (token == SKIP_TOKEN) {
764 // display a modal warning to confirm skip
765 displaySkipWarning();
766 }
767 else if (token == VALUE_ALIAS_TOKEN) {
768 // the icon next to value alias has been touched
769 const nbgl_contentTagValue_t *pair;
770 if (genericContext.currentPairs != NULL) {
771 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
772 }
773 else {
774 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
775 }
776 displayFullValuePage(pair->item, pair->value, pair->extension);
777 }
778 else if (token == BLIND_WARNING_TOKEN) {
779 reviewWithWarnCtx.isIntro = false;
780 reviewWithWarnCtx.warning = NULL;
781 displaySecurityReport(1 << BLIND_SIGNING_WARN);
782 }
783 else if (token == WARNING_BUTTON_TOKEN) {
784 // top-right button, display the security modal
785 reviewWithWarnCtx.securityReportLevel = 1;
786 // if preset is used
787 if (reviewWithWarnCtx.warning->predefinedSet) {
788 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
789 }
790 else {
791 // use customized warning
792 if (reviewWithWarnCtx.isIntro) {
793 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
794 }
795 else {
796 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
797 }
798 }
799 }
800 else if (token == TIP_BOX_TOKEN) {
801 // if warning context is valid and if W3C directly display same as top-right
802 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
803 reviewWithWarnCtx.securityReportLevel = 1;
804 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
805 }
806 else {
807 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
808 }
809 }
810 else { // probably a control provided by caller
811 if (onContentAction != NULL) {
812 onContentAction(token, index, navInfo.activePage);
813 }
814 if (onControls != NULL) {
815 onControls(token, index);
816 }
817 }
818}
819
820// callback used for confirmation
821static void tickerCallback(void)
822{
823 nbgl_pageRelease(pageContext);
824 if (onQuit != NULL) {
825 onQuit();
826 }
827}
828
829// function used to display the current page in review
830static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
831{
832 nbgl_pageContent_t content = {0};
833
834 if ((onNav == NULL) || (onNav(page, &content) == false)) {
835 return;
836 }
837
838 // override some fields
839 content.title = pageTitle;
840 content.isTouchableTitle = true;
841 content.titleToken = QUIT_TOKEN;
842 content.tuneId = TUNE_TAP_CASUAL;
843
844 navInfo.activePage = page;
845 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
846
847 if (forceFullRefresh) {
849 }
850 else {
852 }
853}
854
855// function used to display the current page in review
856static void displayReviewPage(uint8_t page, bool forceFullRefresh)
857{
858 nbgl_pageContent_t content = {0};
859
860 // ensure the page is valid
861 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
862 return;
863 }
864 navInfo.activePage = page;
865 if ((onNav == NULL) || (onNav(navInfo.activePage, &content) == false)) {
866 return;
867 }
868
869 // override some fields
870 content.title = NULL;
871 content.isTouchableTitle = false;
872 content.tuneId = TUNE_TAP_CASUAL;
873
874 if (content.type == INFO_LONG_PRESS) { // last page
875 // for forward only review without known length...
876 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
877 navInfo.nbPages = navInfo.activePage + 1;
878 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
879 if (forwardNavOnly) {
880 // remove the "Skip" button
881 navInfo.skipText = NULL;
882 }
883 }
884
885 // override smallCaseForValue for tag/value types to false
886 if (content.type == TAG_VALUE_DETAILS) {
888 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
889 // button)
891 }
892 else if (content.type == TAG_VALUE_LIST) {
893 content.tagValueList.smallCaseForValue = false;
894 }
895 else if (content.type == TAG_VALUE_CONFIRM) {
897 // use confirm token for black button
898 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
899 }
900
901 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
902
903 if (forceFullRefresh) {
905 }
906 else {
908 }
909}
910
911// Helper that does the computing of which nbgl_content_t and which element in the content should be
912// displayed for the next generic context navigation page
913static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
914 nbgl_content_t *content,
915 uint8_t *p_nbElementsInNextPage,
916 bool *p_flag)
917{
918 int8_t nextContentIdx = genericContext.currentContentIdx;
919 int16_t nextElementIdx = genericContext.currentElementIdx;
920 uint8_t nbElementsInNextPage;
921
922 // Retrieve info on the next page
923 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
924 *p_nbElementsInNextPage = nbElementsInNextPage;
925
926 // Handle forward navigation:
927 // add to current index the number of pairs of the currently active page
928 if (pageIdx > navInfo.activePage) {
929 uint8_t nbElementsInCurrentPage;
930
931 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
932 nextElementIdx += nbElementsInCurrentPage;
933
934 // Handle case where the content to be displayed is in the next content
935 // In such case start at element index 0.
936 // If currentContentElementNb == 0, means not initialized, so skip
937 if ((nextElementIdx >= genericContext.currentContentElementNb)
938 && (genericContext.currentContentElementNb > 0)) {
939 nextContentIdx += 1;
940 nextElementIdx = 0;
941 }
942 }
943
944 // Handle backward navigation:
945 // add to current index the number of pairs of the currently active page
946 if (pageIdx < navInfo.activePage) {
947 // Backward navigation: remove to current index the number of pairs of the current page
948 nextElementIdx -= nbElementsInNextPage;
949
950 // Handle case where the content to be displayed is in the previous content
951 // In such case set a negative number as element index so that it is handled
952 // later once the previous content is accessible so that its elements number
953 // can be retrieved.
954 if (nextElementIdx < 0) {
955 nextContentIdx -= 1;
956 nextElementIdx = -nbElementsInNextPage;
957 }
958 }
959
960 const nbgl_content_t *p_content;
961 // Retrieve next content
962 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
963 p_content = &STARTING_CONTENT;
964 }
965 else if ((nextContentIdx == genericContext.genericContents.nbContents)
966 && (genericContext.hasFinishingContent)) {
967 p_content = &FINISHING_CONTENT;
968 }
969 else {
970 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
971
972 if (p_content == NULL) {
973 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
974 return NULL;
975 }
976 }
977
978 // Handle cases where we are going to display data from a new content:
979 // - navigation to a previous or to the next content
980 // - First page display (genericContext.currentContentElementNb == 0)
981 //
982 // In such case we need to:
983 // - Update genericContext.currentContentIdx
984 // - Update genericContext.currentContentElementNb
985 // - Update onContentAction callback
986 // - Update nextElementIdx in case it was previously not calculable
987 if ((nextContentIdx != genericContext.currentContentIdx)
988 || (genericContext.currentContentElementNb == 0)) {
989 genericContext.currentContentIdx = nextContentIdx;
990 genericContext.currentContentElementNb = getContentNbElement(p_content);
991 onContentAction = PIC(p_content->contentActionCallback);
992 if (nextElementIdx < 0) {
993 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
994 }
995 }
996
997 // Sanity check
998 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
1000 "Invalid element index %d / %d\n",
1001 nextElementIdx,
1002 genericContext.currentContentElementNb);
1003 return NULL;
1004 }
1005
1006 // Update genericContext elements
1007 genericContext.currentElementIdx = nextElementIdx;
1008 navInfo.activePage = pageIdx;
1009
1010 return p_content;
1011}
1012
1013// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
1014// navigation page
1015static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
1016 uint8_t nbElementsInPage,
1017 bool flag,
1018 nbgl_pageContent_t *pageContent)
1019{
1020 uint8_t nextElementIdx = genericContext.currentElementIdx;
1021
1022 pageContent->title = pageTitle;
1023 pageContent->isTouchableTitle = false;
1024 pageContent->titleToken = QUIT_TOKEN;
1025 pageContent->tuneId = TUNE_TAP_CASUAL;
1026
1027 pageContent->type = p_content->type;
1028 switch (pageContent->type) {
1029 case CENTERED_INFO:
1030 memcpy(&pageContent->centeredInfo,
1031 &p_content->content.centeredInfo,
1032 sizeof(pageContent->centeredInfo));
1033 break;
1034 case EXTENDED_CENTER:
1035 memcpy(&pageContent->extendedCenter,
1036 &p_content->content.extendedCenter,
1037 sizeof(pageContent->extendedCenter));
1038 break;
1039 case INFO_LONG_PRESS:
1040 memcpy(&pageContent->infoLongPress,
1041 &p_content->content.infoLongPress,
1042 sizeof(pageContent->infoLongPress));
1043 break;
1044
1045 case INFO_BUTTON:
1046 memcpy(&pageContent->infoButton,
1047 &p_content->content.infoButton,
1048 sizeof(pageContent->infoButton));
1049 break;
1050
1051 case TAG_VALUE_LIST: {
1052 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1053
1054 // memorize pairs (or callback) for usage when alias is used
1055 genericContext.currentPairs = p_content->content.tagValueList.pairs;
1056 genericContext.currentCallback = p_content->content.tagValueList.callback;
1057
1058 if (flag) {
1059 // Flag can be set if the pair is too long to fit or because it needs
1060 // to be displayed as centered info.
1061
1062 // First retrieve the pair
1063 const nbgl_layoutTagValue_t *pair;
1064
1065 if (p_content->content.tagValueList.pairs != NULL) {
1066 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1067 }
1068 else {
1069 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1070 }
1071
1072 if (pair->centeredInfo) {
1073 pageContent->type = EXTENDED_CENTER;
1074 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1075 pair->valueIcon,
1076 pair->item,
1077 pair->value);
1078
1079 // Skip population of nbgl_contentTagValueList_t structure
1080 p_tagValueList = NULL;
1081 }
1082 else {
1083 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1084 pageContent->type = TAG_VALUE_DETAILS;
1085 pageContent->tagValueDetails.detailsButtonText = "More";
1086 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1087 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1088
1089 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1090
1091 // Backup pair info for easy data access upon click on button
1092 genericContext.detailsItem = pair->item;
1093 genericContext.detailsvalue = pair->value;
1094 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1095 }
1096 }
1097
1098 if (p_tagValueList != NULL) {
1099 p_tagValueList->nbPairs = nbElementsInPage;
1100 p_tagValueList->token = p_content->content.tagValueList.token;
1101 if (p_content->content.tagValueList.pairs != NULL) {
1102 p_tagValueList->pairs
1103 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1104 // parse pairs to check if any contains an alias for value
1105 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1106 if (p_tagValueList->pairs[i].aliasValue) {
1107 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1108 break;
1109 }
1110 }
1111 }
1112 else {
1113 p_tagValueList->pairs = NULL;
1114 p_tagValueList->callback = p_content->content.tagValueList.callback;
1115 p_tagValueList->startIndex = nextElementIdx;
1116 // parse pairs to check if any contains an alias for value
1117 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1118 const nbgl_layoutTagValue_t *pair
1119 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1120 if (pair->aliasValue) {
1121 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1122 break;
1123 }
1124 }
1125 }
1126 p_tagValueList->smallCaseForValue = false;
1128 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1129 }
1130
1131 break;
1132 }
1133 case TAG_VALUE_CONFIRM: {
1134 nbgl_contentTagValueList_t *p_tagValueList;
1135 // only display a TAG_VALUE_CONFIRM if we are at the last page
1136 if ((nextElementIdx + nbElementsInPage)
1138 memcpy(&pageContent->tagValueConfirm,
1139 &p_content->content.tagValueConfirm,
1140 sizeof(pageContent->tagValueConfirm));
1141 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1142 }
1143 else {
1144 // else display it as a TAG_VALUE_LIST
1145 pageContent->type = TAG_VALUE_LIST;
1146 p_tagValueList = &pageContent->tagValueList;
1147 }
1148 p_tagValueList->nbPairs = nbElementsInPage;
1149 p_tagValueList->pairs
1150 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1151 // parse pairs to check if any contains an alias for value
1152 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1153 if (p_tagValueList->pairs[i].aliasValue) {
1154 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1155 break;
1156 }
1157 }
1158 // memorize pairs (or callback) for usage when alias is used
1159 genericContext.currentPairs = p_tagValueList->pairs;
1160 genericContext.currentCallback = p_tagValueList->callback;
1161 break;
1162 }
1163 case SWITCHES_LIST:
1164 pageContent->switchesList.nbSwitches = nbElementsInPage;
1165 pageContent->switchesList.switches
1166 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1167 break;
1168 case INFOS_LIST:
1169 pageContent->infosList.nbInfos = nbElementsInPage;
1170 pageContent->infosList.infoTypes
1171 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1172 pageContent->infosList.infoContents
1173 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1174 break;
1175 case CHOICES_LIST:
1176 memcpy(&pageContent->choicesList,
1177 &p_content->content.choicesList,
1178 sizeof(pageContent->choicesList));
1179 pageContent->choicesList.nbChoices = nbElementsInPage;
1180 pageContent->choicesList.names
1181 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1182 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1183 && (p_content->content.choicesList.initChoice
1184 < nextElementIdx + nbElementsInPage)) {
1185 pageContent->choicesList.initChoice
1186 = p_content->content.choicesList.initChoice - nextElementIdx;
1187 }
1188 else {
1189 pageContent->choicesList.initChoice = nbElementsInPage;
1190 }
1191 break;
1192 case BARS_LIST:
1193 pageContent->barsList.nbBars = nbElementsInPage;
1194 pageContent->barsList.barTexts
1195 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1196 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1197 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1198 break;
1199 default:
1200 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1201 return false;
1202 }
1203
1204 bool isFirstOrLastPage
1205 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1206 || (p_content->type == INFO_LONG_PRESS);
1207 nbgl_operationType_t operationType
1208 = (navType == STREAMING_NAV)
1209 ? bundleNavContext.reviewStreaming.operationType
1210 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1211
1212 // if first or last page of review and blind/risky operation, add the warning top-right button
1213 if (isFirstOrLastPage
1214 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1215 // if issue is only Web3Checks "no threat", use privacy icon
1216 if ((operationType & NO_THREAT_OPERATION)
1217 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1218 pageContent->topRightIcon = &PRIVACY_ICON;
1219 }
1220 else {
1221 pageContent->topRightIcon = &WARNING_ICON;
1222 }
1223
1224 pageContent->topRightToken
1225 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1226 }
1227
1228 return true;
1229}
1230
1231// function used to display the current page in generic context navigation mode
1232static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1233{
1234 // Retrieve next page parameters
1235 nbgl_content_t content;
1236 uint8_t nbElementsInPage;
1237 bool flag;
1238 const nbgl_content_t *p_content = NULL;
1239
1240 if (navType == STREAMING_NAV) {
1241 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1242 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1243 bundleNavContext.reviewStreaming.skipCallback();
1244 }
1245 return;
1246 }
1247 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1248 bundleNavReviewStreamingChoice(true);
1249 return;
1250 }
1251 }
1252
1253 if (navInfo.activePage == pageIdx) {
1254 p_content
1255 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1256 }
1257 else if (navInfo.activePage < pageIdx) {
1258 // Support going more than one step forward.
1259 // It occurs when initializing a navigation on an arbitrary page
1260 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1261 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1262 }
1263 }
1264 else {
1265 if (pageIdx - navInfo.activePage > 1) {
1266 // We don't support going more than one step backward as it doesn't occurs for now?
1267 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1268 return;
1269 }
1270 p_content
1271 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1272 }
1273
1274 if (p_content == NULL) {
1275 return;
1276 }
1277
1278 // Create next page content
1279 nbgl_pageContent_t pageContent = {0};
1280 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1281 return;
1282 }
1283
1284 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1285
1286 if (forceFullRefresh) {
1288 }
1289 else {
1291 }
1292}
1293
1294// from the current details context, return a pointer on the details at the given page
1295static const char *getDetailsPageAt(uint8_t detailsPage)
1296{
1297 uint8_t page = 0;
1298 const char *currentChar = detailsContext.value;
1299 while (page < detailsPage) {
1300 uint16_t nbLines
1301 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1302 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1303 uint16_t len;
1304 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1305 currentChar,
1308 &len,
1309 detailsContext.wrapping);
1310 len -= 3;
1311 currentChar = currentChar + len;
1312 }
1313 page++;
1314 }
1315 return currentChar;
1316}
1317
1318// function used to display the current page in details review mode
1319static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1320{
1321 static nbgl_layoutTagValue_t currentPair;
1322 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1323 .nbPages = detailsContext.nbPages,
1324 .navType = NAV_WITH_BUTTONS,
1325 .quitToken = QUIT_TOKEN,
1326 .navWithButtons.navToken = NAV_TOKEN,
1327 .navWithButtons.quitButton = true,
1328 .navWithButtons.backButton = true,
1329 .navWithButtons.quitText = NULL,
1330 .progressIndicator = false,
1331 .tuneId = TUNE_TAP_CASUAL};
1333 .topRightIcon = NULL,
1334 .tagValueList.nbPairs = 1,
1335 .tagValueList.pairs = &currentPair,
1336 .tagValueList.smallCaseForValue = true,
1337 .tagValueList.wrapping = detailsContext.wrapping};
1338
1339 if (modalPageContext != NULL) {
1340 nbgl_pageRelease(modalPageContext);
1341 }
1342 currentPair.item = detailsContext.tag;
1343 // if move backward or first page
1344 if (detailsPage <= detailsContext.currentPage) {
1345 // recompute current start from beginning
1346 currentPair.value = getDetailsPageAt(detailsPage);
1347 forceFullRefresh = true;
1348 }
1349 // else move forward
1350 else {
1351 currentPair.value = detailsContext.nextPageStart;
1352 }
1353 detailsContext.currentPage = detailsPage;
1354 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1355 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1356
1357 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1358 uint16_t len;
1359 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1360 currentPair.value,
1363 &len,
1364 detailsContext.wrapping);
1365 len -= 3;
1366 // memorize next position to save processing
1367 detailsContext.nextPageStart = currentPair.value + len;
1368 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1369 // chars by "..."
1371 }
1372 else {
1373 detailsContext.nextPageStart = NULL;
1374 content.tagValueList.nbMaxLinesForValue = 0;
1375 }
1376 if (info.nbPages == 1) {
1377 // if only one page, no navigation bar, and use a footer instead
1378 info.navWithButtons.quitText = "Close";
1379 }
1380 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1381
1382 if (forceFullRefresh) {
1384 }
1385 else {
1387 }
1388}
1389
1390// function used to display the content of a full value, when touching an alias of a tag/value pair
1391static void displayFullValuePage(const char *backText,
1392 const char *aliasText,
1393 const nbgl_contentValueExt_t *extension)
1394{
1395 const char *modalTitle
1396 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1397 if (extension->aliasType == INFO_LIST_ALIAS) {
1398 genericContext.currentInfos = extension->infolist;
1399 displayInfosListModal(modalTitle, extension->infolist, true);
1400 }
1401 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1402 genericContext.currentTagValues = extension->tagValuelist;
1403 displayTagValueListModal(extension->tagValuelist);
1404 }
1405 else {
1406 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1407 .withLeftBorder = true,
1408 .onActionCallback = &modalLayoutTouchCallback,
1409 .tapActionText = NULL};
1411 .separationLine = false,
1412 .backAndText.token = 0,
1413 .backAndText.tuneId = TUNE_TAP_CASUAL,
1414 .backAndText.text = modalTitle};
1415 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1416 // add header with the tag part of the pair, to go back
1417 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1418 // add either QR Code or full value text
1419 if (extension->aliasType == QR_CODE_ALIAS) {
1420#ifdef NBGL_QRCODE
1421 nbgl_layoutQRCode_t qrCode
1422 = {.url = extension->fullValue,
1423 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1424 .text2 = extension->explanation,
1425 .centered = true,
1426 .offsetY = 0};
1427
1428 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1429#endif // NBGL_QRCODE
1430 }
1431 else {
1432 const char *info;
1433 // add full value text
1434 if (extension->aliasType == ENS_ALIAS) {
1435 info = "ENS names are resolved by Ledger backend.";
1436 }
1437 else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
1438 info = "This account label comes from your Address Book in Ledger Live.";
1439 }
1440 else {
1441 info = extension->explanation;
1442 }
1444 genericContext.modalLayout, aliasText, extension->fullValue, info);
1445 }
1446 // draw & refresh
1447 nbgl_layoutDraw(genericContext.modalLayout);
1448 nbgl_refresh();
1449 }
1450}
1451
1452// function used to display the modal containing tip-box infos
1453static void displayInfosListModal(const char *modalTitle,
1454 const nbgl_contentInfoList_t *infos,
1455 bool fromReview)
1456{
1458 .nbPages = 1,
1459 .navType = NAV_WITH_BUTTONS,
1460 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1461 .navWithButtons.navToken = NAV_TOKEN,
1462 .navWithButtons.quitButton = false,
1463 .navWithButtons.backButton = true,
1464 .navWithButtons.quitText = NULL,
1465 .progressIndicator = false,
1466 .tuneId = TUNE_TAP_CASUAL};
1467 nbgl_pageContent_t content
1468 = {.type = INFOS_LIST,
1469 .topRightIcon = NULL,
1470 .infosList.nbInfos = infos->nbInfos,
1471 .infosList.withExtensions = infos->withExtensions,
1472 .infosList.infoTypes = infos->infoTypes,
1473 .infosList.infoContents = infos->infoContents,
1474 .infosList.infoExtensions = infos->infoExtensions,
1475 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1476 .title = modalTitle,
1477 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1478 .tuneId = TUNE_TAP_CASUAL};
1479
1480 if (modalPageContext != NULL) {
1481 nbgl_pageRelease(modalPageContext);
1482 }
1483 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1484
1486}
1487
1488// function used to display the modal containing alias tag-value pairs
1489static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1490{
1491 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1492 .nbPages = detailsContext.nbPages,
1493 .navType = NAV_WITH_BUTTONS,
1494 .quitToken = QUIT_TOKEN,
1495 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1496 .navWithButtons.quitButton = true,
1497 .navWithButtons.backButton = true,
1498 .navWithButtons.quitText = NULL,
1499 .progressIndicator = false,
1500 .tuneId = TUNE_TAP_CASUAL};
1502 .topRightIcon = NULL,
1503 .tagValueList.smallCaseForValue = true,
1504 .tagValueList.wrapping = detailsContext.wrapping};
1505 uint8_t nbElementsInPage;
1506
1507 // if first page or forward
1508 if (detailsContext.currentPage <= pageIdx) {
1509 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1510 }
1511 else {
1512 // backward direction
1513 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1514 detailsContext.currentPairIdx -= nbElementsInPage;
1515 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1516 detailsContext.currentPairIdx -= nbElementsInPage;
1517 }
1518 detailsContext.currentPage = pageIdx;
1519
1520 content.tagValueList.pairs
1521 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1522 content.tagValueList.nbPairs = nbElementsInPage;
1523 detailsContext.currentPairIdx += nbElementsInPage;
1524 if (info.nbPages == 1) {
1525 // if only one page, no navigation bar, and use a footer instead
1526 info.navWithButtons.quitText = "Close";
1527 }
1528
1529 if (modalPageContext != NULL) {
1530 nbgl_pageRelease(modalPageContext);
1531 }
1532 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1533
1534 if (forceFullRefresh) {
1536 }
1537 else {
1539 }
1540}
1541
1542#ifdef NBGL_QRCODE
1543static void displayAddressQRCode(void)
1544{
1545 // display the address as QR Code
1546 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1547 .withLeftBorder = true,
1548 .onActionCallback = &modalLayoutTouchCallback,
1549 .tapActionText = NULL};
1550 nbgl_layoutHeader_t headerDesc = {
1551 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1552 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1553 .text1 = NULL,
1554 .centered = true,
1555 .offsetY = 0};
1556
1557 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1558 // add empty header for better look
1559 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1560 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1561 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1562 addressConfirmationContext.tagValuePairs[0].value,
1564 false);
1565
1566 if (nbLines <= QRCODE_NB_MAX_LINES) {
1567 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1568 }
1569 else {
1570 // only keep beginning and end of text, and add ... in the middle
1571 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1572 addressConfirmationContext.tagValuePairs[0].value,
1574 QRCODE_NB_MAX_LINES,
1575 reducedAddress,
1576 QRCODE_REDUCED_ADDR_LEN);
1577 qrCode.text2 = reducedAddress; // in gray
1578 }
1579
1580 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1581
1583 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1584 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1585 nbgl_refresh();
1586}
1587
1588#endif // NBGL_QRCODE
1589
1590// called when header is touched on modal page, to dismiss it
1591static void modalLayoutTouchCallback(int token, uint8_t index)
1592{
1593 UNUSED(index);
1594 if (token == DISMISS_QR_TOKEN) {
1595 // dismiss modal
1596 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1597 addressConfirmationContext.modalLayout = NULL;
1599 }
1600 else if (token == DISMISS_WARNING_TOKEN) {
1601 // dismiss modal
1602 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1603 // if already at first level, simply redraw the background
1604 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1605 reviewWithWarnCtx.modalLayout = NULL;
1607 }
1608 else {
1609 // We are at level 2 of warning modal, so re-display the level 1
1610 reviewWithWarnCtx.securityReportLevel = 1;
1611 // if preset is used
1612 if (reviewWithWarnCtx.warning->predefinedSet) {
1613 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1614 }
1615 else {
1616 // use customized warning
1617 const nbgl_warningDetails_t *details
1618 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1619 : reviewWithWarnCtx.warning->reviewDetails;
1620 displayCustomizedSecurityReport(details);
1621 }
1622 return;
1623 }
1624 }
1625 else if (token == DISMISS_DETAILS_TOKEN) {
1626 // dismiss modal
1627 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1628 // if already at first level, simply redraw the background
1629 if (choiceWithDetailsCtx.level <= 1) {
1630 choiceWithDetailsCtx.modalLayout = NULL;
1632 }
1633 else {
1634 // We are at level 2 of details modal, so re-display the level 1
1635 choiceWithDetailsCtx.level = 1;
1636 choiceWithDetailsCtx.modalLayout
1637 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1638 }
1639 }
1640 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1641 if (sharedContext.usage == SHARE_CTX_REVIEW_WITH_WARNING) {
1642 // dismiss modal before creating a new one
1643 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1644 reviewWithWarnCtx.securityReportLevel = 2;
1645 // if preset is used
1646 if (reviewWithWarnCtx.warning->predefinedSet) {
1647 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1648 }
1649 else {
1650 // use customized warning
1651 const nbgl_warningDetails_t *details
1652 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1653 : reviewWithWarnCtx.warning->reviewDetails;
1654 if (details->type == BAR_LIST_WARNING) {
1655 displayCustomizedSecurityReport(
1656 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1657 }
1658 }
1659 }
1660 else if (sharedContext.usage == SHARE_CTX_CHOICE_WITH_DETAILS) {
1661 const nbgl_warningDetails_t *details
1662 = &choiceWithDetailsCtx.details->barList.details[token - FIRST_WARN_BAR_TOKEN];
1663 if (details->title != NO_TYPE_WARNING) {
1664 // dismiss modal before creating a new one
1665 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1666 choiceWithDetailsCtx.level = 2;
1667 choiceWithDetailsCtx.modalLayout
1668 = displayModalDetails(details, DISMISS_DETAILS_TOKEN);
1669 }
1670 }
1671 return;
1672 }
1673 else {
1674 // dismiss modal
1675 nbgl_layoutRelease(genericContext.modalLayout);
1676 genericContext.modalLayout = NULL;
1678 }
1679 nbgl_refresh();
1680}
1681
1682// called when item are touched in layout
1683static void layoutTouchCallback(int token, uint8_t index)
1684{
1685 // choice buttons in initial warning page
1686 if (token == WARNING_CHOICE_TOKEN) {
1687 if (index == 0) { // top button to exit
1688 reviewWithWarnCtx.choiceCallback(false);
1689 }
1690 else { // bottom button to continue to review
1691 reviewWithWarnCtx.isIntro = false;
1692 if (reviewWithWarnCtx.isStreaming) {
1693 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1694 reviewWithWarnCtx.icon,
1695 reviewWithWarnCtx.reviewTitle,
1696 reviewWithWarnCtx.reviewSubTitle,
1697 reviewWithWarnCtx.choiceCallback,
1698 false);
1699 }
1700 else {
1701 useCaseReview(reviewWithWarnCtx.operationType,
1702 reviewWithWarnCtx.tagValueList,
1703 reviewWithWarnCtx.icon,
1704 reviewWithWarnCtx.reviewTitle,
1705 reviewWithWarnCtx.reviewSubTitle,
1706 reviewWithWarnCtx.finishTitle,
1707 &activeTipBox,
1708 reviewWithWarnCtx.choiceCallback,
1709 false,
1710 false);
1711 }
1712 }
1713 }
1714 // top-right button in initial warning page
1715 else if (token == WARNING_BUTTON_TOKEN) {
1716 // start directly at first level of security report
1717 reviewWithWarnCtx.securityReportLevel = 1;
1718 // if preset is used
1719 if (reviewWithWarnCtx.warning->predefinedSet) {
1720 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1721 }
1722 else {
1723 // use customized warning
1724 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1725 ? reviewWithWarnCtx.warning->introDetails
1726 : reviewWithWarnCtx.warning->reviewDetails;
1727 displayCustomizedSecurityReport(details);
1728 }
1729 }
1730 // top-right button in choice with details page
1731 else if (token == CHOICE_DETAILS_TOKEN) {
1732 choiceWithDetailsCtx.level = 1;
1733 choiceWithDetailsCtx.modalLayout
1734 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1735 }
1736}
1737
1738// called when skip button is touched in footer, during forward only review
1739static void displaySkipWarning(void)
1740{
1742 .cancelText = "Go back to review",
1743 .centeredInfo.text1 = "Skip review?",
1744 .centeredInfo.text2
1745 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1746 .centeredInfo.text3 = NULL,
1747 .centeredInfo.style = LARGE_CASE_INFO,
1748 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1749 .centeredInfo.offsetY = 0,
1750 .confirmationText = "Yes, skip",
1751 .confirmationToken = SKIP_TOKEN,
1752 .tuneId = TUNE_TAP_CASUAL,
1753 .modal = true};
1754 if (modalPageContext != NULL) {
1755 nbgl_pageRelease(modalPageContext);
1756 }
1757 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1759}
1760
1761#ifdef NBGL_KEYPAD
1762// called to update the keypad and automatically show / hide:
1763// - backspace key if no digits are present
1764// - validation key if the min digit is reached
1765// - keypad if the max number of digit is reached
1766static void updateKeyPad(bool add)
1767{
1768 bool enableValidate, enableBackspace, enableDigits;
1769 bool redrawKeypad = false;
1771
1772 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1773 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1774 enableBackspace = (keypadContext.pinLen > 0);
1775 if (add) {
1776 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1777 || // activate "validate" button on keypad
1778 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1779 || // deactivate "digits" on keypad
1780 (keypadContext.pinLen == 1)) { // activate "backspace"
1781 redrawKeypad = true;
1782 }
1783 }
1784 else { // remove
1785 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1786 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1787 || // deactivate "validate" button on keypad
1788 (keypadContext.pinLen
1789 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1790 redrawKeypad = true;
1791 }
1792 }
1793 if (keypadContext.hidden == true) {
1794 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1795 }
1796 else {
1798 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1799 }
1800 if (redrawKeypad) {
1802 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1803 }
1804
1805 if ((!add) && (keypadContext.pinLen == 0)) {
1806 // Full refresh to fully clean the bullets when reaching 0 digits
1807 mode = FULL_COLOR_REFRESH;
1808 }
1810}
1811
1812// called when a key is touched on the keypad
1813static void keypadCallback(char touchedKey)
1814{
1815 switch (touchedKey) {
1816 case BACKSPACE_KEY:
1817 if (keypadContext.pinLen > 0) {
1818 keypadContext.pinLen--;
1819 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1820 }
1821 updateKeyPad(false);
1822 break;
1823
1824 case VALIDATE_KEY:
1825 // Gray out keyboard / buttons as a first user feedback
1826 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1829
1830 onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1831 break;
1832
1833 default:
1834 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1835 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1836 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1837 keypadContext.pinLen++;
1838 }
1839 updateKeyPad(true);
1840 }
1841 break;
1842 }
1843}
1844
1845// called to create a keypad, with either hidden or visible digits
1846static void keypadGenericUseCase(const char *title,
1847 uint8_t minDigits,
1848 uint8_t maxDigits,
1849 uint8_t backToken,
1850 bool shuffled,
1851 bool hidden,
1852 tune_index_e tuneId,
1853 nbgl_pinValidCallback_t validatePinCallback,
1854 nbgl_layoutTouchCallback_t actionCallback)
1855{
1856 nbgl_layoutDescription_t layoutDescription = {0};
1858 .separationLine = true,
1859 .backAndText.token = backToken,
1860 .backAndText.tuneId = tuneId,
1861 .backAndText.text = NULL};
1862 int status = -1;
1863
1864 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
1865 return;
1866 }
1867
1868 reset_callbacks();
1869 // reset the keypad context
1870 memset(&keypadContext, 0, sizeof(KeypadContext_t));
1871
1872 // get a layout
1873 layoutDescription.onActionCallback = actionCallback;
1874 layoutDescription.modal = false;
1875 layoutDescription.withLeftBorder = false;
1876 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
1877 keypadContext.hidden = hidden;
1878
1879 // set back key in header
1880 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
1881
1882 // add keypad
1883 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
1884 if (status < 0) {
1885 return;
1886 }
1887 // add keypad content
1889 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
1890
1891 if (status < 0) {
1892 return;
1893 }
1894
1895 // validation pin callback
1896 onValidatePin = validatePinCallback;
1897 // pin code acceptable lengths
1898 keypadContext.pinMinDigits = minDigits;
1899 keypadContext.pinMaxDigits = maxDigits;
1900
1901 nbgl_layoutDraw(keypadContext.layoutCtx);
1903}
1904#endif
1905
1920static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
1921 const nbgl_contentTagValueList_t *tagValueList,
1922 uint8_t startIndex,
1923 bool isSkippable,
1924 bool hasConfirmationButton,
1925 bool hasDetailsButton,
1926 bool *requireSpecificDisplay)
1927{
1928 uint8_t nbPairsInPage = 0;
1929 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
1930 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
1931
1932 // if the review is skippable, it means that there is less height for tag/value pairs
1933 // the small centering header becomes a touchable header
1934 if (isSkippable) {
1935 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
1936 }
1937
1938 *requireSpecificDisplay = false;
1939 while (nbPairsInPage < nbPairs) {
1940 const nbgl_layoutTagValue_t *pair;
1941 nbgl_font_id_e value_font;
1942 uint16_t nbLines;
1943
1944 // margin between pairs
1945 // 12 or 24 px between each tag/value pair
1946 if (nbPairsInPage > 0) {
1947 currentHeight += INTER_TAG_VALUE_MARGIN;
1948 }
1949 // fetch tag/value pair strings.
1950 if (tagValueList->pairs != NULL) {
1951 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
1952 }
1953 else {
1954 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
1955 }
1956
1957 if (pair->forcePageStart && nbPairsInPage > 0) {
1958 // This pair must be at the top of a page
1959 break;
1960 }
1961
1962 if (pair->centeredInfo) {
1963 if (nbPairsInPage > 0) {
1964 // This pair must be at the top of a page
1965 break;
1966 }
1967 else {
1968 // This pair is the only one of the page and has a specific display behavior
1969 nbPairsInPage = 1;
1970 *requireSpecificDisplay = true;
1971 break;
1972 }
1973 }
1974
1975 // tag height
1976 currentHeight += nbgl_getTextHeightInWidth(
1977 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
1978 // space between tag and value
1979 currentHeight += 4;
1980 // set value font
1981 if (tagValueList->smallCaseForValue) {
1982 value_font = SMALL_REGULAR_FONT;
1983 }
1984 else {
1985 value_font = LARGE_MEDIUM_FONT;
1986 }
1987 // value height
1988 currentHeight += nbgl_getTextHeightInWidth(
1989 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1990 // nb lines for value
1992 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1993 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
1994 if (nbPairsInPage == 0) {
1995 // Pair too long to fit in a single screen
1996 // It will be the only one of the page and has a specific display behavior
1997 nbPairsInPage = 1;
1998 *requireSpecificDisplay = true;
1999 }
2000 break;
2001 }
2002 nbPairsInPage++;
2003 }
2004 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
2005 // let's check if it still fits with a CONFIRMATION button, and if not,
2006 // remove the last pair
2007 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
2008 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
2009 if (currentHeight > maxUsableHeight) {
2010 nbPairsInPage--;
2011 }
2012 }
2013 // do the same with just a details button
2014 else if (hasDetailsButton) {
2015 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
2016 if (currentHeight > maxUsableHeight) {
2017 nbPairsInPage--;
2018 }
2019 }
2020 return nbPairsInPage;
2021}
2022
2032static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
2033 const nbgl_contentTagValueList_t *tagValueList,
2034 uint8_t startIndex)
2035{
2036 uint8_t nbPairsInPage = 0;
2037 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2038 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2039
2040 while (nbPairsInPage < nbPairs) {
2041 const nbgl_layoutTagValue_t *pair;
2042
2043 // margin between pairs
2044 // 12 or 24 px between each tag/value pair
2045 if (nbPairsInPage > 0) {
2046 currentHeight += INTER_TAG_VALUE_MARGIN;
2047 }
2048 // fetch tag/value pair strings.
2049 if (tagValueList->pairs != NULL) {
2050 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2051 }
2052 else {
2053 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2054 }
2055
2056 // tag height
2057 currentHeight += nbgl_getTextHeightInWidth(
2058 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2059 // space between tag and value
2060 currentHeight += 4;
2061
2062 // value height
2063 currentHeight += nbgl_getTextHeightInWidth(
2064 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2065
2066 // we have reached the maximum height, it means than there are to many pairs
2067 if (currentHeight >= maxUsableHeight) {
2068 break;
2069 }
2070 nbPairsInPage++;
2071 }
2072 return nbPairsInPage;
2073}
2074
2075static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2076 uint8_t pageIdxStart,
2077 bool isLast,
2078 bool isSkippable)
2079{
2080 uint8_t nbElements = 0;
2081 uint8_t nbPages = 0;
2082 uint8_t nbElementsInPage;
2083 uint8_t elemIdx = 0;
2084 bool flag;
2085
2086 nbElements = getContentNbElement(content);
2087
2088 while (nbElements > 0) {
2089 flag = 0;
2090 // if the current page is not the first one (or last), a navigation bar exists
2091 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2092 if (content->type == TAG_VALUE_LIST) {
2093 nbElementsInPage = getNbTagValuesInPage(nbElements,
2094 &content->content.tagValueList,
2095 elemIdx,
2096 isSkippable,
2097 false,
2098 false,
2099 &flag);
2100 }
2101 else if (content->type == TAG_VALUE_CONFIRM) {
2102 nbElementsInPage = getNbTagValuesInPage(nbElements,
2104 elemIdx,
2105 isSkippable,
2106 isLast,
2107 !isLast,
2108 &flag);
2109 }
2110 else if (content->type == INFOS_LIST) {
2111 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
2112 nbElements, &content->content.infosList, elemIdx, hasNav);
2113 }
2114 else if (content->type == SWITCHES_LIST) {
2115 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
2116 nbElements, &content->content.switchesList, elemIdx, hasNav);
2117 }
2118 else if (content->type == BARS_LIST) {
2119 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
2120 nbElements, &content->content.barsList, elemIdx, hasNav);
2121 }
2122 else if (content->type == CHOICES_LIST) {
2123 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2124 nbElements, &content->content.choicesList, elemIdx, hasNav);
2125 }
2126 else {
2127 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2128 }
2129
2130 elemIdx += nbElementsInPage;
2131 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2132 nbElements -= nbElementsInPage;
2133 nbPages++;
2134 }
2135
2136 return nbPages;
2137}
2138
2139static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2140 uint8_t pageIdxStart,
2141 bool isSkippable)
2142{
2143 uint8_t nbPages = 0;
2144 nbgl_content_t content;
2145 const nbgl_content_t *p_content;
2146
2147 for (int i = 0; i < genericContents->nbContents; i++) {
2148 p_content = getContentAtIdx(genericContents, i, &content);
2149 if (p_content == NULL) {
2150 return 0;
2151 }
2152 nbPages += getNbPagesForContent(p_content,
2153 pageIdxStart + nbPages,
2154 (i == (genericContents->nbContents - 1)),
2155 isSkippable);
2156 }
2157
2158 return nbPages;
2159}
2160
2161static void prepareAddressConfirmationPages(const char *address,
2162 const nbgl_contentTagValueList_t *tagValueList,
2163 nbgl_content_t *firstPageContent,
2164 nbgl_content_t *secondPageContent)
2165{
2166 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2167
2168 addressConfirmationContext.tagValuePairs[0].item = "Address";
2169 addressConfirmationContext.tagValuePairs[0].value = address;
2170 addressConfirmationContext.nbPairs = 1;
2171
2172 // First page
2173 firstPageContent->type = TAG_VALUE_CONFIRM;
2174 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2175
2176#ifdef NBGL_QRCODE
2177 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2178 // only use "Show as QR" when address & pairs are not fitting in a single page
2179 if ((tagValueList != NULL) && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2181 bool flag;
2182 // copy in intermediate structure
2183 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2184 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2185 &tagValueList->pairs[i],
2186 sizeof(nbgl_contentTagValue_t));
2187 addressConfirmationContext.nbPairs++;
2188 }
2189 // check how many can fit in a page
2190 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2191 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2192 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2193 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2194 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2195 // if they don't all fit, keep only the address
2196 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2197 addressConfirmationContext.nbPairs = 1;
2198 }
2199 }
2200 else {
2201 tagValueConfirm->detailsButtonText = NULL;
2202 }
2203 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2204#else // NBGL_QRCODE
2205 tagValueConfirm->detailsButtonText = NULL;
2206 tagValueConfirm->detailsButtonIcon = NULL;
2207#endif // NBGL_QRCODE
2208 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2209 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2210 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2211 tagValueConfirm->tagValueList.smallCaseForValue = false;
2212 tagValueConfirm->tagValueList.nbMaxLinesForValue = 0;
2213 tagValueConfirm->tagValueList.wrapping = false;
2214 // if it's an extended address verif, it takes 2 pages, so display a "Tap to continue", and
2215 // no confirmation button
2216 if ((tagValueList != NULL)
2217 && (tagValueList->nbPairs > (addressConfirmationContext.nbPairs - 1))) {
2218 tagValueConfirm->detailsButtonText = "Show as QR";
2219 tagValueConfirm->confirmationText = NULL;
2220 // the second page is dedicated to the extended tag/value pairs
2221 secondPageContent->type = TAG_VALUE_CONFIRM;
2222 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2223 tagValueConfirm->confirmationText = "Confirm";
2224 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2225 tagValueConfirm->detailsButtonText = NULL;
2226 tagValueConfirm->detailsButtonIcon = NULL;
2227 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2228 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2229 tagValueConfirm->tagValueList.nbPairs
2230 = tagValueList->nbPairs - (addressConfirmationContext.nbPairs - 1);
2231 tagValueConfirm->tagValueList.pairs
2232 = &tagValueList->pairs[addressConfirmationContext.nbPairs - 1];
2233 }
2234 else {
2235 // otherwise no tap to continue but a confirmation button
2236 tagValueConfirm->confirmationText = "Confirm";
2237 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2238 }
2239}
2240
2241static void bundleNavStartHome(void)
2242{
2243 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2244
2245 useCaseHomeExt(context->appName,
2246 context->appIcon,
2247 context->tagline,
2248 context->settingContents != NULL ? true : false,
2249 &context->homeAction,
2250 bundleNavStartSettings,
2251 context->quitCallback);
2252}
2253
2254static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2255{
2256 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2257
2258 nbgl_useCaseGenericSettings(context->appName,
2259 initSettingPage,
2260 context->settingContents,
2261 context->infosList,
2262 bundleNavStartHome);
2263}
2264
2265static void bundleNavStartSettings(void)
2266{
2267 bundleNavStartSettingsAtPage(0);
2268}
2269
2270static void bundleNavReviewConfirmRejection(void)
2271{
2272 bundleNavContext.review.choiceCallback(false);
2273}
2274
2275static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2276 nbgl_callback_t callback)
2277{
2278 const char *title;
2279 const char *confirmText;
2280 // clear skip and blind bits
2281 operationType
2282 &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION);
2283 if (operationType == TYPE_TRANSACTION) {
2284 title = "Reject transaction?";
2285 confirmText = "Go back to transaction";
2286 }
2287 else if (operationType == TYPE_MESSAGE) {
2288 title = "Reject message?";
2289 confirmText = "Go back to message";
2290 }
2291 else {
2292 title = "Reject operation?";
2293 confirmText = "Go back to operation";
2294 }
2295
2296 // display a choice to confirm/cancel rejection
2297 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2298}
2299
2300static void bundleNavReviewChoice(bool confirm)
2301{
2302 if (confirm) {
2303 bundleNavContext.review.choiceCallback(true);
2304 }
2305 else {
2306 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2307 bundleNavReviewConfirmRejection);
2308 }
2309}
2310
2311static void bundleNavReviewStreamingConfirmRejection(void)
2312{
2313 bundleNavContext.reviewStreaming.choiceCallback(false);
2314}
2315
2316static void bundleNavReviewStreamingChoice(bool confirm)
2317{
2318 if (confirm) {
2319 // Display a spinner if it wasn't the finish step
2320 if (STARTING_CONTENT.type != INFO_LONG_PRESS) {
2321 nbgl_useCaseSpinner("Processing");
2322 }
2323 bundleNavContext.reviewStreaming.choiceCallback(true);
2324 }
2325 else {
2326 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2327 bundleNavReviewStreamingConfirmRejection);
2328 }
2329}
2330
2331// function used to display the details in modal (from Choice with details or from Customized
2332// security report) it can be either a single page with text or QR code, or a list of touchable bars
2333// leading to single pages
2334static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token)
2335{
2336 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2337 .withLeftBorder = true,
2338 .onActionCallback = modalLayoutTouchCallback,
2339 .tapActionText = NULL};
2341 .separationLine = true,
2342 .backAndText.icon = NULL,
2343 .backAndText.tuneId = TUNE_TAP_CASUAL,
2344 .backAndText.token = token};
2345 uint8_t i;
2346 nbgl_layout_t *layout;
2347
2348 layout = nbgl_layoutGet(&layoutDescription);
2349 headerDesc.backAndText.text = details->title;
2350 nbgl_layoutAddHeader(layout, &headerDesc);
2351 if (details->type == BAR_LIST_WARNING) {
2352 // if more than one warning, so use a list of touchable bars
2353 for (i = 0; i < details->barList.nbBars; i++) {
2354 nbgl_layoutBar_t bar;
2355 bar.text = details->barList.texts[i];
2356 bar.subText = details->barList.subTexts[i];
2357 bar.iconRight
2358 = (details->barList.details[i].type != NO_TYPE_WARNING) ? &PUSH_ICON : NULL;
2359 bar.iconLeft = details->barList.icons[i];
2360 bar.token = FIRST_WARN_BAR_TOKEN + i;
2361 bar.tuneId = TUNE_TAP_CASUAL;
2362 bar.large = false;
2363 bar.inactive = false;
2364 nbgl_layoutAddTouchableBar(layout, &bar);
2366 }
2367 }
2368 else if (details->type == QRCODE_WARNING) {
2369#ifdef NBGL_QRCODE
2370 // display a QR Code
2371 nbgl_layoutAddQRCode(layout, &details->qrCode);
2372#endif // NBGL_QRCODE
2373 headerDesc.backAndText.text = details->title;
2374 }
2375 else if (details->type == CENTERED_INFO_WARNING) {
2376 // display a centered info
2377 nbgl_layoutAddContentCenter(layout, &details->centeredInfo);
2378 headerDesc.separationLine = false;
2379 }
2380 nbgl_layoutDraw(layout);
2381 nbgl_refresh();
2382 return layout;
2383}
2384
2385// function used to display the security level page in modal
2386// it can be either a single page with text or QR code, or a list of touchable bars leading
2387// to single pages
2388static void displaySecurityReport(uint32_t set)
2389{
2390 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2391 .withLeftBorder = true,
2392 .onActionCallback = modalLayoutTouchCallback,
2393 .tapActionText = NULL};
2395 .separationLine = true,
2396 .backAndText.icon = NULL,
2397 .backAndText.tuneId = TUNE_TAP_CASUAL,
2398 .backAndText.token = DISMISS_WARNING_TOKEN};
2399 nbgl_layoutFooter_t footerDesc
2400 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2401 uint8_t i;
2402 uint8_t nbWarnings = 0;
2403 const char *provider;
2404
2405 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2406
2407 // count the number of warnings
2408 for (i = 0; i < NB_WARNING_TYPES; i++) {
2409 if ((set & (1 << i)) && (i != W3C_NO_THREAT_WARN)) {
2410 nbWarnings++;
2411 }
2412 }
2413
2414 // display a list of touchable bars in some conditions:
2415 // - we must be in the first level of security report
2416 // - and be in the review itself (not from the intro/warning screen)
2417 //
2418 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2419 for (i = 0; i < NB_WARNING_TYPES; i++) {
2420 if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) {
2421 nbgl_layoutBar_t bar = {0};
2422 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2423 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2424 bar.subText = securityReportItems[i].subText;
2425 }
2426 else {
2427 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2428 }
2429 bar.text = securityReportItems[i].text;
2430 if (i != W3C_NO_THREAT_WARN) {
2431 bar.iconRight = &PUSH_ICON;
2432 bar.token = FIRST_WARN_BAR_TOKEN + i;
2433 bar.tuneId = TUNE_TAP_CASUAL;
2434 }
2435 else {
2437 }
2438 bar.iconLeft = securityReportItems[i].icon;
2439 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2440 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2441 }
2442 }
2443 headerDesc.backAndText.text = "Security report";
2444 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2445 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2446 nbgl_refresh();
2447 return;
2448 }
2449 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2450 provider = reviewWithWarnCtx.warning->reportProvider;
2451 }
2452 else {
2453 provider = "[unknown]";
2454 }
2455 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2456 size_t urlLen = 0;
2457 char *destStr
2458 = tmpString
2459 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2460#ifdef NBGL_QRCODE
2461 // display a QR Code
2462 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2463 .text1 = reviewWithWarnCtx.warning->reportUrl,
2464 .text2 = "Scan to view full report",
2465 .centered = true,
2466 .offsetY = 0};
2467 // add "https://"" as prefix of the given URL
2468 snprintf(destStr,
2469 W3C_DESCRIPTION_MAX_LEN / 2,
2470 "https://%s",
2471 reviewWithWarnCtx.warning->reportUrl);
2472 urlLen = strlen(destStr) + 1;
2473 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2474 footerDesc.emptySpace.height = 24;
2475#endif // NBGL_QRCODE
2476 // use the next part of destStr for back bar text
2477 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2478 headerDesc.backAndText.text = destStr + urlLen;
2479 }
2480 else if (set & (1 << BLIND_SIGNING_WARN)) {
2481 // display a centered if in review
2482 nbgl_contentCenter_t info = {0};
2483 info.icon = &LARGE_WARNING_ICON;
2484 info.title = "This transaction cannot be Clear Signed";
2485 info.description
2486 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2487 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2488 "ledger.com/e8";
2489 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2490 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2491 headerDesc.separationLine = false;
2492 }
2493 else if (set & (1 << W3C_ISSUE_WARN)) {
2494 // if W3 Checks issue, display a centered info
2495 nbgl_contentCenter_t info = {0};
2496 info.icon = &LARGE_WARNING_ICON;
2497 info.title = "Transaction Check unavailable";
2498 info.description
2499 = "If you're not using the\nLedger Live app, Transaction Check might not work. If your "
2500 "are using Ledger Live, reject the transaction and try again.\n\nGet help at "
2501 "ledger.com/e11";
2502 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2503 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2504 headerDesc.separationLine = false;
2505 }
2506 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2507 if (footerDesc.emptySpace.height > 0) {
2508 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2509 }
2510 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2511 nbgl_refresh();
2512}
2513
2514// function used to display the security level page in modal
2515// it can be either a single page with text or QR code, or a list of touchable bars leading
2516// to single pages
2517static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2518{
2519 reviewWithWarnCtx.modalLayout = displayModalDetails(details, DISMISS_WARNING_TOKEN);
2520}
2521
2522// function used to display the initial warning page when starting a "review with warning"
2523static void displayInitialWarning(void)
2524{
2525 // Play notification sound
2526#ifdef HAVE_PIEZO_SOUND
2527 tune_index_e tune = TUNE_RESERVED;
2528#endif // HAVE_PIEZO_SOUND
2529 nbgl_layoutDescription_t layoutDescription;
2530 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2531 .token = WARNING_CHOICE_TOKEN,
2532 .topText = "Back to safety",
2533 .style = ROUNDED_AND_FOOTER_STYLE,
2534 .tuneId = TUNE_TAP_CASUAL};
2535 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2536 .separationLine = false,
2537 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2538 uint32_t set = reviewWithWarnCtx.warning->predefinedSet
2539 & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2540
2541 reviewWithWarnCtx.isIntro = true;
2542
2543 layoutDescription.modal = false;
2544 layoutDescription.withLeftBorder = true;
2545
2546 layoutDescription.onActionCallback = layoutTouchCallback;
2547 layoutDescription.tapActionText = NULL;
2548
2549 layoutDescription.ticker.tickerCallback = NULL;
2550 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2551
2552 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2553 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2555 reviewWithWarnCtx.layoutCtx,
2556 (set == (1 << BLIND_SIGNING_WARN)) ? &INFO_I_ICON : &QRCODE_ICON,
2557 WARNING_BUTTON_TOKEN,
2558 TUNE_TAP_CASUAL);
2559 }
2560 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2561 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2562 reviewWithWarnCtx.warning->introTopRightIcon,
2563 WARNING_BUTTON_TOKEN,
2564 TUNE_TAP_CASUAL);
2565 }
2566 // add main content
2567 // if predefined content is configured, use it preferably
2568 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2569 nbgl_contentCenter_t info = {0};
2570
2571 const char *provider;
2572
2573 // default icon
2574 info.icon = &LARGE_WARNING_ICON;
2575
2576 // use small title only if not Blind signing only
2577 if (set != (1 << BLIND_SIGNING_WARN)) {
2578 if (reviewWithWarnCtx.warning->reportProvider) {
2579 provider = reviewWithWarnCtx.warning->reportProvider;
2580 }
2581 else {
2582 provider = "[unknown]";
2583 }
2584 info.smallTitle = tmpString;
2585 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2586 }
2587
2588 if (set == (1 << BLIND_SIGNING_WARN)) {
2589 info.title = "Blind signing ahead";
2590 info.description
2591 = "This transaction's details are not fully verifiable. If you sign, you could "
2592 "lose all your assets.";
2593 buttonsInfo.bottomText = "Continue anyway";
2594#ifdef HAVE_PIEZO_SOUND
2595 tune = TUNE_NEUTRAL;
2596#endif // HAVE_PIEZO_SOUND
2597 }
2598 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2599 info.title = "Potential risk";
2600 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2601 info.description = "Unidentified risk";
2602 }
2603 else {
2604 info.description = reviewWithWarnCtx.warning->providerMessage;
2605 }
2606 buttonsInfo.bottomText = "Accept risk and continue";
2607#ifdef HAVE_PIEZO_SOUND
2608 tune = TUNE_NEUTRAL;
2609#endif // HAVE_PIEZO_SOUND
2610 }
2611 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2612 info.title = "Critical threat";
2613 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2614 info.description = "Unidentified threat";
2615 }
2616 else {
2617 info.description = reviewWithWarnCtx.warning->providerMessage;
2618 }
2619 buttonsInfo.bottomText = "Accept threat and continue";
2620#ifdef HAVE_PIEZO_SOUND
2621 tune = TUNE_ERROR;
2622#endif // HAVE_PIEZO_SOUND
2623 }
2624 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2625 }
2626 else if (reviewWithWarnCtx.warning->info != NULL) {
2627 // if no predefined content, use custom one
2628 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2629#ifdef HAVE_PIEZO_SOUND
2630 tune = TUNE_LOOK_AT_ME;
2631#endif // HAVE_PIEZO_SOUND
2632 }
2633 // add button and footer on bottom
2634 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2635
2636#ifdef HAVE_PIEZO_SOUND
2637 if (tune != TUNE_RESERVED) {
2638 os_io_seph_cmd_piezo_play_tune(tune);
2639 }
2640#endif // HAVE_PIEZO_SOUND
2641 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2642 nbgl_refresh();
2643}
2644
2645// function to factorize code for reviews tipbox
2646static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2647{
2648 const char *predefinedTipBoxText = NULL;
2649
2650 // if warning is valid and a warning requires a tip-box
2651 if (reviewWithWarnCtx.warning) {
2652 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2653 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2654 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2655 }
2656 else {
2657 predefinedTipBoxText = "Transaction Check unavailable";
2658 }
2659 }
2660 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2661 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2662 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2663 }
2664 else {
2665 predefinedTipBoxText = "Critical threat detected.";
2666 }
2667 }
2668 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2669 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2670 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2671 }
2672 else {
2673 predefinedTipBoxText = "Potential risk detected.";
2674 }
2675 }
2676 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2677 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2678 predefinedTipBoxText
2679 = "No threat detected by Transaction Check but blind signing required.";
2680 }
2681 else {
2682 predefinedTipBoxText = "No threat detected by Transaction Check.";
2683 }
2684 }
2685 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2686 predefinedTipBoxText = "Blind signing required.";
2687 }
2688 }
2689
2690 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2691 // do not display "Swipe to review" if a tip-box is displayed
2692 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2693 if (predefinedTipBoxText != NULL) {
2694 genericContext.validWarningCtx = true;
2695 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2696 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2697 }
2698 else {
2699 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2700 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2701 }
2702 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2703 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2704 }
2705}
2706
2707// function to factorize code for all simple reviews
2708static void useCaseReview(nbgl_operationType_t operationType,
2709 const nbgl_contentTagValueList_t *tagValueList,
2710 const nbgl_icon_details_t *icon,
2711 const char *reviewTitle,
2712 const char *reviewSubTitle,
2713 const char *finishTitle,
2714 const nbgl_tipBox_t *tipBox,
2715 nbgl_choiceCallback_t choiceCallback,
2716 bool isLight,
2717 bool playNotifSound)
2718{
2719 reset_callbacks();
2720 memset(&genericContext, 0, sizeof(genericContext));
2721
2722 bundleNavContext.review.operationType = operationType;
2723 bundleNavContext.review.choiceCallback = choiceCallback;
2724
2725 // memorize context
2726 onChoice = bundleNavReviewChoice;
2727 navType = GENERIC_NAV;
2728 pageTitle = NULL;
2729
2730 genericContext.genericContents.contentsList = localContentsList;
2731 genericContext.genericContents.nbContents = 3;
2732 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2733
2734 // First a centered info
2735 STARTING_CONTENT.type = EXTENDED_CENTER;
2736 prepareReviewFirstPage(
2737 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2738
2739 // Prepare un tipbox if needed
2740 initWarningTipBox(tipBox);
2741
2742 // Then the tag/value pairs
2743 localContentsList[1].type = TAG_VALUE_LIST;
2744 memcpy(&localContentsList[1].content.tagValueList,
2745 tagValueList,
2747 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2748
2749 // The last page
2750 if (isLight) {
2751 localContentsList[2].type = INFO_BUTTON;
2752 prepareReviewLightLastPage(
2753 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2754 }
2755 else {
2756 localContentsList[2].type = INFO_LONG_PRESS;
2757 prepareReviewLastPage(
2758 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2759 }
2760
2761 // compute number of pages & fill navigation structure
2762 uint8_t nbPages = getNbPagesForGenericContents(
2763 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2764 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2765
2766 // Play notification sound if required
2767 if (playNotifSound) {
2768#ifdef HAVE_PIEZO_SOUND
2769 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2770#endif // HAVE_PIEZO_SOUND
2771 }
2772
2773 displayGenericContextPage(0, true);
2774}
2775
2776// function to factorize code for all streaming reviews
2777static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
2778 const nbgl_icon_details_t *icon,
2779 const char *reviewTitle,
2780 const char *reviewSubTitle,
2781 nbgl_choiceCallback_t choiceCallback,
2782 bool playNotifSound)
2783{
2784 reset_callbacks();
2785 memset(&genericContext, 0, sizeof(genericContext));
2786
2787 bundleNavContext.reviewStreaming.operationType = operationType;
2788 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
2789 bundleNavContext.reviewStreaming.icon = icon;
2790
2791 // memorize context
2792 onChoice = bundleNavReviewStreamingChoice;
2793 navType = STREAMING_NAV;
2794 pageTitle = NULL;
2795
2796 genericContext.genericContents.contentsList = localContentsList;
2797 genericContext.genericContents.nbContents = 1;
2798 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
2799
2800 // First a centered info
2801 STARTING_CONTENT.type = EXTENDED_CENTER;
2802 prepareReviewFirstPage(
2803 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2804
2805 // Prepare un tipbox if needed
2806 initWarningTipBox(NULL);
2807
2808 // compute number of pages & fill navigation structure
2809 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
2810 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2811 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
2812 // no back button on first page
2813 navInfo.navWithButtons.backButton = false;
2814
2815 // Play notification sound if required
2816 if (playNotifSound) {
2817#ifdef HAVE_PIEZO_SOUND
2818 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2819#endif // HAVE_PIEZO_SOUND
2820 }
2821
2822 displayGenericContextPage(0, true);
2823}
2824
2841static void useCaseHomeExt(const char *appName,
2842 const nbgl_icon_details_t *appIcon,
2843 const char *tagline,
2844 bool withSettings,
2845 nbgl_homeAction_t *homeAction,
2846 nbgl_callback_t topRightCallback,
2847 nbgl_callback_t quitCallback)
2848{
2849 reset_callbacks();
2850
2851 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
2852 .centeredInfo.text1 = appName,
2853 .centeredInfo.text3 = NULL,
2854 .centeredInfo.style = LARGE_CASE_INFO,
2855 .centeredInfo.offsetY = 0,
2856 .footerText = NULL,
2857 .bottomButtonStyle = QUIT_APP_TEXT,
2858 .tapActionText = NULL,
2859 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
2860 .topRightToken = CONTINUE_TOKEN,
2861 .tuneId = TUNE_TAP_CASUAL};
2862 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
2863 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
2864 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
2865 onAction = homeAction->callback;
2866 info.actionButtonText = homeAction->text;
2867 info.actionButtonIcon = homeAction->icon;
2870 }
2871 else {
2872 info.bottomButtonsToken = QUIT_TOKEN;
2873 onAction = NULL;
2874 info.actionButtonText = NULL;
2875 info.actionButtonIcon = NULL;
2876 }
2877 if (tagline == NULL) {
2878 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
2879 snprintf(tmpString,
2881 "This app enables signing\ntransactions on its network.");
2882 }
2883 else {
2884 snprintf(tmpString,
2886 "%s %s\n%s",
2888 appName,
2890 }
2891
2892 // If there is more than 3 lines, it means the appName was split, so we put it on the next
2893 // line
2894 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
2895 snprintf(tmpString,
2897 "%s\n%s %s",
2899 appName,
2901 }
2902 info.centeredInfo.text2 = tmpString;
2903 }
2904 else {
2905 info.centeredInfo.text2 = tagline;
2906 }
2907
2908 onContinue = topRightCallback;
2909 onQuit = quitCallback;
2910
2911 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
2913}
2914
2923static void displayDetails(const char *tag, const char *value, bool wrapping)
2924{
2925 memset(&detailsContext, 0, sizeof(detailsContext));
2926
2927 uint16_t nbLines
2928 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
2929
2930 // initialize context
2931 detailsContext.tag = tag;
2932 detailsContext.value = value;
2933 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
2934 detailsContext.currentPage = 0;
2935 detailsContext.wrapping = wrapping;
2936 // add some spare for room lost with "..." substitution
2937 if (detailsContext.nbPages > 1) {
2938 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
2939 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
2940 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
2941 uint8_t nbLinesInLastPage
2942 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
2943
2944 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
2945 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
2947 detailsContext.nbPages++;
2948 }
2949 }
2950
2951 displayDetailsPage(0, true);
2952}
2953
2954// function used to display the modal containing alias tag-value pairs
2955static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues)
2956{
2957 uint8_t nbElements = 0;
2958 uint8_t nbElementsInPage;
2959 uint8_t elemIdx = 0;
2960
2961 // initialize context
2962 memset(&detailsContext, 0, sizeof(detailsContext));
2963 nbElements = tagValues->nbPairs;
2964
2965 while (nbElements > 0) {
2966 nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx);
2967
2968 elemIdx += nbElementsInPage;
2969 modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage);
2970 nbElements -= nbElementsInPage;
2971 detailsContext.nbPages++;
2972 }
2973
2974 displayTagValueListModalPage(0, true);
2975}
2976
2977/**********************
2978 * GLOBAL FUNCTIONS
2979 **********************/
2980
2993uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
2994 const nbgl_contentTagValueList_t *tagValueList,
2995 uint8_t startIndex,
2996 bool *requireSpecificDisplay)
2997{
2998 return getNbTagValuesInPage(
2999 nbPairs, tagValueList, startIndex, false, false, false, requireSpecificDisplay);
3000}
3001
3015uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
3016 const nbgl_contentTagValueList_t *tagValueList,
3017 uint8_t startIndex,
3018 bool isSkippable,
3019 bool *requireSpecificDisplay)
3020{
3021 return getNbTagValuesInPage(
3022 nbPairs, tagValueList, startIndex, isSkippable, false, false, requireSpecificDisplay);
3023}
3024
3034uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
3035 const nbgl_contentInfoList_t *infosList,
3036 uint8_t startIndex,
3037 bool withNav)
3038{
3039 uint8_t nbInfosInPage = 0;
3040 uint16_t currentHeight = 0;
3041 uint16_t previousHeight;
3042 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3043 const char *const *infoContents = PIC(infosList->infoContents);
3044
3045 while (nbInfosInPage < nbInfos) {
3046 // The type string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3047 currentHeight
3048 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
3049
3050 // content height
3051 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
3052 PIC(infoContents[startIndex + nbInfosInPage]),
3054 true);
3055 // if height is over the limit
3056 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3057 // if there was no nav, now there will be, so it can be necessary to remove the last
3058 // item
3059 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3060 nbInfosInPage--;
3061 }
3062 break;
3063 }
3064 previousHeight = currentHeight;
3065 nbInfosInPage++;
3066 }
3067 return nbInfosInPage;
3068}
3069
3079uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
3080 const nbgl_contentSwitchesList_t *switchesList,
3081 uint8_t startIndex,
3082 bool withNav)
3083{
3084 uint8_t nbSwitchesInPage = 0;
3085 uint16_t currentHeight = 0;
3086 uint16_t previousHeight = 0;
3087 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3088 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
3089
3090 while (nbSwitchesInPage < nbSwitches) {
3091 nbgl_contentSwitch_t *curSwitch = &switchArray[startIndex + nbSwitchesInPage];
3092 // The text string is either a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3093 // or we use its height directly
3094 uint16_t textHeight = MAX(
3095 LIST_ITEM_MIN_TEXT_HEIGHT,
3096 nbgl_getTextHeightInWidth(SMALL_BOLD_FONT, curSwitch->text, AVAILABLE_WIDTH, true));
3097 currentHeight += textHeight + 2 * LIST_ITEM_PRE_HEADING;
3098
3099 if (curSwitch->subText) {
3100 currentHeight += LIST_ITEM_HEADING_SUB_TEXT;
3101
3102 // sub-text height
3103 currentHeight += nbgl_getTextHeightInWidth(
3104 SMALL_REGULAR_FONT, curSwitch->subText, AVAILABLE_WIDTH, true);
3105 }
3106 // if height is over the limit
3107 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3108 break;
3109 }
3110 previousHeight = currentHeight;
3111 nbSwitchesInPage++;
3112 }
3113 // if there was no nav, now there will be, so it can be necessary to remove the last
3114 // item
3115 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3116 nbSwitchesInPage--;
3117 }
3118 return nbSwitchesInPage;
3119}
3120
3130uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
3131 const nbgl_contentBarsList_t *barsList,
3132 uint8_t startIndex,
3133 bool withNav)
3134{
3135 uint8_t nbBarsInPage = 0;
3136 uint16_t currentHeight = 0;
3137 uint16_t previousHeight;
3138 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3139
3140 UNUSED(barsList);
3141 UNUSED(startIndex);
3142
3143 while (nbBarsInPage < nbBars) {
3144 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3145 // if height is over the limit
3146 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3147 break;
3148 }
3149 previousHeight = currentHeight;
3150 nbBarsInPage++;
3151 }
3152 // if there was no nav, now there may will be, so it can be necessary to remove the last
3153 // item
3154 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3155 nbBarsInPage--;
3156 }
3157 return nbBarsInPage;
3158}
3159
3169uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
3170 const nbgl_contentRadioChoice_t *choicesList,
3171 uint8_t startIndex,
3172 bool withNav)
3173{
3174 uint8_t nbChoicesInPage = 0;
3175 uint16_t currentHeight = 0;
3176 uint16_t previousHeight;
3177 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3178
3179 UNUSED(choicesList);
3180 UNUSED(startIndex);
3181
3182 while (nbChoicesInPage < nbChoices) {
3183 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3184 // if height is over the limit
3185 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3186 // if there was no nav, now there will be, so it can be necessary to remove the last
3187 // item
3188 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3189 nbChoicesInPage--;
3190 }
3191 break;
3192 }
3193 previousHeight = currentHeight;
3194 nbChoicesInPage++;
3195 }
3196 return nbChoicesInPage;
3197}
3198
3206{
3207 uint8_t nbPages = 0;
3208 uint8_t nbPairs = tagValueList->nbPairs;
3209 uint8_t nbPairsInPage;
3210 uint8_t i = 0;
3211 bool flag;
3212
3213 while (i < tagValueList->nbPairs) {
3214 // upper margin
3215 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
3216 i += nbPairsInPage;
3217 nbPairs -= nbPairsInPage;
3218 nbPages++;
3219 }
3220 return nbPages;
3221}
3222
3227void nbgl_useCaseHome(const char *appName,
3228 const nbgl_icon_details_t *appIcon,
3229 const char *tagline,
3230 bool withSettings,
3231 nbgl_callback_t topRightCallback,
3232 nbgl_callback_t quitCallback)
3233{
3234 nbgl_homeAction_t homeAction = {0};
3235 useCaseHomeExt(
3236 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3237}
3238
3243void nbgl_useCaseHomeExt(const char *appName,
3244 const nbgl_icon_details_t *appIcon,
3245 const char *tagline,
3246 bool withSettings,
3247 const char *actionButtonText,
3248 nbgl_callback_t actionCallback,
3249 nbgl_callback_t topRightCallback,
3250 nbgl_callback_t quitCallback)
3251{
3252 nbgl_homeAction_t homeAction = {.callback = actionCallback,
3253 .icon = NULL,
3254 .style = STRONG_HOME_ACTION,
3255 .text = actionButtonText};
3256
3257 useCaseHomeExt(
3258 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3259}
3260
3274void nbgl_useCaseNavigableContent(const char *title,
3275 uint8_t initPage,
3276 uint8_t nbPages,
3277 nbgl_callback_t quitCallback,
3278 nbgl_navCallback_t navCallback,
3279 nbgl_layoutTouchCallback_t controlsCallback)
3280{
3281 reset_callbacks();
3282
3283 // memorize context
3284 onQuit = quitCallback;
3285 onNav = navCallback;
3286 onControls = controlsCallback;
3287 pageTitle = title;
3288 navType = SETTINGS_NAV;
3289
3290 // fill navigation structure
3291 prepareNavInfo(false, nbPages, NULL);
3292
3293 displaySettingsPage(initPage, true);
3294}
3295
3301void nbgl_useCaseSettings(const char *title,
3302 uint8_t initPage,
3303 uint8_t nbPages,
3304 bool touchable,
3305 nbgl_callback_t quitCallback,
3306 nbgl_navCallback_t navCallback,
3307 nbgl_layoutTouchCallback_t controlsCallback)
3308{
3309 UNUSED(touchable);
3311 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3312}
3313
3326void nbgl_useCaseGenericSettings(const char *appName,
3327 uint8_t initPage,
3328 const nbgl_genericContents_t *settingContents,
3329 const nbgl_contentInfoList_t *infosList,
3330 nbgl_callback_t quitCallback)
3331{
3332 reset_callbacks();
3333 memset(&genericContext, 0, sizeof(genericContext));
3334
3335 // memorize context
3336 onQuit = quitCallback;
3337 pageTitle = appName;
3338 navType = GENERIC_NAV;
3339
3340 if (settingContents != NULL) {
3341 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3342 }
3343 if (infosList != NULL) {
3344 genericContext.hasFinishingContent = true;
3345 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3346 FINISHING_CONTENT.type = INFOS_LIST;
3347 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3348 }
3349
3350 // fill navigation structure
3351 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3352 if (infosList != NULL) {
3353 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3354 }
3355
3356 prepareNavInfo(false, nbPages, NULL);
3357
3358 displayGenericContextPage(initPage, true);
3359}
3360
3372void nbgl_useCaseGenericConfiguration(const char *title,
3373 uint8_t initPage,
3374 const nbgl_genericContents_t *contents,
3375 nbgl_callback_t quitCallback)
3376{
3377 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3378}
3379
3397 const char *appName,
3398 const nbgl_icon_details_t *appIcon,
3399 const char *tagline,
3400 const uint8_t
3401 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3402 const nbgl_genericContents_t *settingContents,
3403 const nbgl_contentInfoList_t *infosList,
3404 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3405 nbgl_callback_t quitCallback)
3406{
3407 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3408
3409 context->appName = appName;
3410 context->appIcon = appIcon;
3411 context->tagline = tagline;
3412 context->settingContents = settingContents;
3413 context->infosList = infosList;
3414 if (action != NULL) {
3415 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3416 }
3417 else {
3418 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3419 }
3420 context->quitCallback = quitCallback;
3421
3422 if (initSettingPage != INIT_HOME_PAGE) {
3423 bundleNavStartSettingsAtPage(initSettingPage);
3424 }
3425 else {
3426 bundleNavStartHome();
3427 }
3428}
3429
3437void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3438{
3439 reset_callbacks();
3440
3441 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3442 .tickerIntervale = 0, // not periodic
3443 .tickerValue = STATUS_SCREEN_DURATION};
3444
3445 onQuit = quitCallback;
3446 if (isSuccess) {
3447#ifdef HAVE_PIEZO_SOUND
3448 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3449#endif // HAVE_PIEZO_SOUND
3450
3451 pageContext = nbgl_pageDrawLedgerInfo(&pageCallback, &ticker, message, QUIT_TOKEN);
3452 }
3453 else {
3455 .footerText = NULL,
3456 .centeredInfo.icon = &DENIED_CIRCLE_ICON,
3457 .centeredInfo.offsetY = SMALL_FOOTER_HEIGHT / 2,
3458 .centeredInfo.onTop = false,
3459 .centeredInfo.style = LARGE_CASE_INFO,
3460 .centeredInfo.text1 = message,
3461 .centeredInfo.text2 = NULL,
3462 .centeredInfo.text3 = NULL,
3463 .tapActionText = "",
3464 .isSwipeable = false,
3465 .tapActionToken = QUIT_TOKEN,
3466 .topRightStyle = NO_BUTTON_STYLE,
3467 .actionButtonText = NULL,
3468 .tuneId = TUNE_TAP_CASUAL};
3469 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3470 }
3472}
3473
3481 nbgl_callback_t quitCallback)
3482{
3483 const char *msg;
3484 bool isSuccess;
3485 switch (reviewStatusType) {
3487 msg = "Operation signed";
3488 isSuccess = true;
3489 break;
3491 msg = "Operation rejected";
3492 isSuccess = false;
3493 break;
3495 msg = "Transaction signed";
3496 isSuccess = true;
3497 break;
3499 msg = "Transaction rejected";
3500 isSuccess = false;
3501 break;
3503 msg = "Message signed";
3504 isSuccess = true;
3505 break;
3507 msg = "Message rejected";
3508 isSuccess = false;
3509 break;
3511 msg = "Address verified";
3512 isSuccess = true;
3513 break;
3515 msg = "Address verification\ncancelled";
3516 isSuccess = false;
3517 break;
3518 default:
3519 return;
3520 }
3521 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3522}
3523
3537 const char *message,
3538 const char *subMessage,
3539 const char *confirmText,
3540 const char *cancelText,
3541 nbgl_choiceCallback_t callback)
3542{
3543 reset_callbacks();
3544
3545 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3546 .centeredInfo.text1 = message,
3547 .centeredInfo.text2 = subMessage,
3548 .centeredInfo.text3 = NULL,
3549 .centeredInfo.style = LARGE_CASE_INFO,
3550 .centeredInfo.icon = icon,
3551 .centeredInfo.offsetY = 0,
3552 .confirmationText = confirmText,
3553 .confirmationToken = CHOICE_TOKEN,
3554 .tuneId = TUNE_TAP_CASUAL,
3555 .modal = false};
3556 // check params
3557 if ((confirmText == NULL) || (cancelText == NULL)) {
3558 return;
3559 }
3560 onChoice = callback;
3561 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3563}
3564
3580 const char *message,
3581 const char *subMessage,
3582 const char *confirmText,
3583 const char *cancelText,
3584 nbgl_genericDetails_t *details,
3585 nbgl_choiceCallback_t callback)
3586{
3587 reset_callbacks();
3588 nbgl_layoutDescription_t layoutDescription;
3589 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = cancelText,
3590 .token = CHOICE_TOKEN,
3591 .topText = confirmText,
3592 .style = ROUNDED_AND_FOOTER_STYLE,
3593 .tuneId = TUNE_TAP_CASUAL};
3594 nbgl_contentCenter_t centeredInfo = {0};
3595 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
3596 .separationLine = false,
3597 .emptySpace.height = MEDIUM_CENTERING_HEADER};
3598
3599 // check params
3600 if ((confirmText == NULL) || (cancelText == NULL)) {
3601 return;
3602 }
3603 onChoice = callback;
3604 layoutDescription.modal = false;
3605 layoutDescription.withLeftBorder = true;
3606
3607 layoutDescription.onActionCallback = layoutTouchCallback;
3608 layoutDescription.tapActionText = NULL;
3609
3610 layoutDescription.ticker.tickerCallback = NULL;
3611 sharedContext.usage = SHARE_CTX_CHOICE_WITH_DETAILS;
3612 choiceWithDetailsCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
3613 choiceWithDetailsCtx.details = details;
3614
3615 nbgl_layoutAddHeader(choiceWithDetailsCtx.layoutCtx, &headerDesc);
3616 nbgl_layoutAddChoiceButtons(choiceWithDetailsCtx.layoutCtx, &buttonsInfo);
3617 centeredInfo.icon = icon;
3618 centeredInfo.title = message;
3619 centeredInfo.description = subMessage;
3620 nbgl_layoutAddContentCenter(choiceWithDetailsCtx.layoutCtx, &centeredInfo);
3621
3622 if (details != NULL) {
3624 choiceWithDetailsCtx.layoutCtx, &SEARCH_ICON, CHOICE_DETAILS_TOKEN, TUNE_TAP_CASUAL);
3625 }
3626
3627 nbgl_layoutDraw(choiceWithDetailsCtx.layoutCtx);
3629}
3630
3644void nbgl_useCaseConfirm(const char *message,
3645 const char *subMessage,
3646 const char *confirmText,
3647 const char *cancelText,
3648 nbgl_callback_t callback)
3649{
3650 // Don't reset callback or nav context as this is just a modal.
3651
3652 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3653 .centeredInfo.text1 = message,
3654 .centeredInfo.text2 = subMessage,
3655 .centeredInfo.text3 = NULL,
3656 .centeredInfo.style = LARGE_CASE_INFO,
3657 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3658 .centeredInfo.offsetY = 0,
3659 .confirmationText = confirmText,
3660 .confirmationToken = CHOICE_TOKEN,
3661 .tuneId = TUNE_TAP_CASUAL,
3662 .modal = true};
3663 onModalConfirm = callback;
3664 if (modalPageContext != NULL) {
3665 nbgl_pageRelease(modalPageContext);
3666 }
3667 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3669}
3670
3682 const char *message,
3683 const char *actionText,
3684 nbgl_callback_t callback)
3685{
3686 nbgl_pageContent_t content = {0};
3687
3688 // memorize callback
3689 onAction = callback;
3690
3691 content.tuneId = TUNE_TAP_CASUAL;
3692 content.type = INFO_BUTTON;
3693 content.infoButton.buttonText = actionText;
3694 content.infoButton.text = message;
3695 content.infoButton.icon = icon;
3696 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3697
3698 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3700}
3701
3714 const char *reviewTitle,
3715 const char *reviewSubTitle,
3716 const char *rejectText,
3717 nbgl_callback_t continueCallback,
3718 nbgl_callback_t rejectCallback)
3719{
3720 reset_callbacks();
3721
3722 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3723 .footerToken = QUIT_TOKEN,
3724 .tapActionText = NULL,
3725 .isSwipeable = true,
3726 .tapActionToken = CONTINUE_TOKEN,
3727 .topRightStyle = NO_BUTTON_STYLE,
3728 .actionButtonText = NULL,
3729 .tuneId = TUNE_TAP_CASUAL};
3730 info.centeredInfo.icon = icon;
3731 info.centeredInfo.text1 = reviewTitle;
3732 info.centeredInfo.text2 = reviewSubTitle;
3733 info.centeredInfo.text3 = "Swipe to review";
3735 info.centeredInfo.offsetY = 0;
3736 onQuit = rejectCallback;
3737 onContinue = continueCallback;
3738
3739#ifdef HAVE_PIEZO_SOUND
3740 // Play notification sound
3741 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3742#endif // HAVE_PIEZO_SOUND
3743
3744 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3745 nbgl_refresh();
3746}
3747
3752void nbgl_useCaseRegularReview(uint8_t initPage,
3753 uint8_t nbPages,
3754 const char *rejectText,
3755 nbgl_layoutTouchCallback_t buttonCallback,
3756 nbgl_navCallback_t navCallback,
3757 nbgl_choiceCallback_t choiceCallback)
3758{
3759 reset_callbacks();
3760
3761 // memorize context
3762 onChoice = choiceCallback;
3763 onNav = navCallback;
3764 onControls = buttonCallback;
3765 forwardNavOnly = false;
3766 navType = REVIEW_NAV;
3767
3768 // fill navigation structure
3769 UNUSED(rejectText);
3770 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3771
3772 displayReviewPage(initPage, true);
3773}
3774
3788 const nbgl_pageInfoLongPress_t *infoLongPress,
3789 const char *rejectText,
3790 nbgl_choiceCallback_t callback)
3791{
3792 uint8_t offset = 0;
3793
3794 reset_callbacks();
3795 memset(&genericContext, 0, sizeof(genericContext));
3796
3797 // memorize context
3798 onChoice = callback;
3799 navType = GENERIC_NAV;
3800 pageTitle = NULL;
3801 bundleNavContext.review.operationType = TYPE_OPERATION;
3802
3803 genericContext.genericContents.contentsList = localContentsList;
3804 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3805
3806 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3807 localContentsList[offset].type = TAG_VALUE_LIST;
3808 memcpy(&localContentsList[offset].content.tagValueList,
3809 tagValueList,
3811 offset++;
3812 }
3813
3814 localContentsList[offset].type = INFO_LONG_PRESS;
3815 memcpy(&localContentsList[offset].content.infoLongPress,
3816 infoLongPress,
3817 sizeof(nbgl_pageInfoLongPress_t));
3818 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3819 offset++;
3820
3821 genericContext.genericContents.nbContents = offset;
3822
3823 // compute number of pages & fill navigation structure
3824 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3825 UNUSED(rejectText);
3826 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3827
3828 displayGenericContextPage(0, true);
3829}
3830
3845 const nbgl_pageInfoLongPress_t *infoLongPress,
3846 const char *rejectText,
3847 nbgl_choiceCallback_t callback)
3848{
3849 uint8_t offset = 0;
3850
3851 reset_callbacks();
3852 memset(&genericContext, 0, sizeof(genericContext));
3853
3854 // memorize context
3855 onChoice = callback;
3856 navType = GENERIC_NAV;
3857 pageTitle = NULL;
3858
3859 genericContext.genericContents.contentsList = localContentsList;
3860 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3861
3862 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3863 localContentsList[offset].type = TAG_VALUE_LIST;
3864 memcpy(&localContentsList[offset].content.tagValueList,
3865 tagValueList,
3867 offset++;
3868 }
3869
3870 localContentsList[offset].type = INFO_BUTTON;
3871 localContentsList[offset].content.infoButton.text = infoLongPress->text;
3872 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
3873 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
3874 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
3875 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
3876 offset++;
3877
3878 genericContext.genericContents.nbContents = offset;
3879
3880 // compute number of pages & fill navigation structure
3881 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3882 UNUSED(rejectText);
3883 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3884
3885 displayGenericContextPage(0, true);
3886}
3887
3904void nbgl_useCaseReview(nbgl_operationType_t operationType,
3905 const nbgl_contentTagValueList_t *tagValueList,
3906 const nbgl_icon_details_t *icon,
3907 const char *reviewTitle,
3908 const char *reviewSubTitle,
3909 const char *finishTitle,
3910 nbgl_choiceCallback_t choiceCallback)
3911{
3912 useCaseReview(operationType,
3913 tagValueList,
3914 icon,
3915 reviewTitle,
3916 reviewSubTitle,
3917 finishTitle,
3918 NULL,
3919 choiceCallback,
3920 false,
3921 true);
3922}
3923
3944 const nbgl_contentTagValueList_t *tagValueList,
3945 const nbgl_icon_details_t *icon,
3946 const char *reviewTitle,
3947 const char *reviewSubTitle,
3948 const char *finishTitle,
3949 const nbgl_tipBox_t *tipBox,
3950 nbgl_choiceCallback_t choiceCallback)
3951{
3952 nbgl_useCaseAdvancedReview(operationType,
3953 tagValueList,
3954 icon,
3955 reviewTitle,
3956 reviewSubTitle,
3957 finishTitle,
3958 tipBox,
3959 &blindSigningWarning,
3960 choiceCallback);
3961}
3962
3986 const nbgl_contentTagValueList_t *tagValueList,
3987 const nbgl_icon_details_t *icon,
3988 const char *reviewTitle,
3989 const char *reviewSubTitle,
3990 const char *finishTitle,
3991 const nbgl_tipBox_t *tipBox,
3992 const nbgl_warning_t *warning,
3993 nbgl_choiceCallback_t choiceCallback)
3994{
3995 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3996 // memorize tipBox because it can be in the call stack of the caller
3997 if (tipBox != NULL) {
3998 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
3999 }
4000 else {
4001 memset(&activeTipBox, 0, sizeof(activeTipBox));
4002 }
4003 // if no warning at all, it's a simple review
4004 if ((warning == NULL)
4005 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4006 && (warning->reviewDetails == NULL))) {
4007 useCaseReview(operationType,
4008 tagValueList,
4009 icon,
4010 reviewTitle,
4011 reviewSubTitle,
4012 finishTitle,
4013 tipBox,
4014 choiceCallback,
4015 false,
4016 true);
4017 return;
4018 }
4019 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4020 operationType |= NO_THREAT_OPERATION;
4021 }
4022 else {
4023 operationType |= RISKY_OPERATION;
4024 }
4025 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4026 reviewWithWarnCtx.isStreaming = false;
4027 reviewWithWarnCtx.operationType = operationType;
4028 reviewWithWarnCtx.tagValueList = tagValueList;
4029 reviewWithWarnCtx.icon = icon;
4030 reviewWithWarnCtx.reviewTitle = reviewTitle;
4031 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4032 reviewWithWarnCtx.finishTitle = finishTitle;
4033 reviewWithWarnCtx.warning = warning;
4034 reviewWithWarnCtx.choiceCallback = choiceCallback;
4035
4036 // display the initial warning only of a risk/threat or blind signing
4037 if ((!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
4038 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
4039 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)))
4040 && (warning->introDetails == NULL)) {
4041 useCaseReview(operationType,
4042 tagValueList,
4043 icon,
4044 reviewTitle,
4045 reviewSubTitle,
4046 finishTitle,
4047 tipBox,
4048 choiceCallback,
4049 false,
4050 true);
4051 return;
4052 }
4053
4054 displayInitialWarning();
4055}
4056
4074 const nbgl_contentTagValueList_t *tagValueList,
4075 const nbgl_icon_details_t *icon,
4076 const char *reviewTitle,
4077 const char *reviewSubTitle,
4078 const char *finishTitle,
4079 nbgl_choiceCallback_t choiceCallback)
4080{
4081 useCaseReview(operationType,
4082 tagValueList,
4083 icon,
4084 reviewTitle,
4085 reviewSubTitle,
4086 finishTitle,
4087 NULL,
4088 choiceCallback,
4089 true,
4090 true);
4091}
4092
4102 const char *rejectText,
4103 nbgl_callback_t rejectCallback)
4104{
4105 reset_callbacks();
4106 memset(&genericContext, 0, sizeof(genericContext));
4107
4108 // memorize context
4109 onQuit = rejectCallback;
4110 navType = GENERIC_NAV;
4111 pageTitle = NULL;
4112 bundleNavContext.review.operationType = TYPE_OPERATION;
4113
4114 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
4115
4116 // compute number of pages & fill navigation structure
4117 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4118 prepareNavInfo(true, nbPages, rejectText);
4119 navInfo.quitToken = QUIT_TOKEN;
4120
4121#ifdef HAVE_PIEZO_SOUND
4122 // Play notification sound
4123 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4124#endif // HAVE_PIEZO_SOUND
4125
4126 displayGenericContextPage(0, true);
4127}
4128
4142 const nbgl_icon_details_t *icon,
4143 const char *reviewTitle,
4144 const char *reviewSubTitle,
4145 nbgl_choiceCallback_t choiceCallback)
4146{
4147 useCaseReviewStreamingStart(
4148 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4149}
4150
4165 const nbgl_icon_details_t *icon,
4166 const char *reviewTitle,
4167 const char *reviewSubTitle,
4168 nbgl_choiceCallback_t choiceCallback)
4169{
4171 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4172}
4173
4190 const nbgl_icon_details_t *icon,
4191 const char *reviewTitle,
4192 const char *reviewSubTitle,
4193 const nbgl_warning_t *warning,
4194 nbgl_choiceCallback_t choiceCallback)
4195{
4196 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
4197 // if no warning at all, it's a simple review
4198 if ((warning == NULL)
4199 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4200 && (warning->reviewDetails == NULL))) {
4201 useCaseReviewStreamingStart(
4202 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4203 return;
4204 }
4205 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4206 operationType |= NO_THREAT_OPERATION;
4207 }
4208 else {
4209 operationType |= RISKY_OPERATION;
4210 }
4211
4212 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4213 reviewWithWarnCtx.isStreaming = true;
4214 reviewWithWarnCtx.operationType = operationType;
4215 reviewWithWarnCtx.icon = icon;
4216 reviewWithWarnCtx.reviewTitle = reviewTitle;
4217 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4218 reviewWithWarnCtx.choiceCallback = choiceCallback;
4219 reviewWithWarnCtx.warning = warning;
4220
4221 // display the initial warning only of a risk/threat or blind signing
4222 if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
4223 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
4224 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
4225 useCaseReviewStreamingStart(
4226 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4227 return;
4228 }
4229 displayInitialWarning();
4230}
4231
4246 nbgl_choiceCallback_t choiceCallback,
4247 nbgl_callback_t skipCallback)
4248{
4249 // Should follow a call to nbgl_useCaseReviewStreamingStart
4250 memset(&genericContext, 0, sizeof(genericContext));
4251
4252 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4253 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4254
4255 // memorize context
4256 onChoice = bundleNavReviewStreamingChoice;
4257 navType = STREAMING_NAV;
4258 pageTitle = NULL;
4259
4260 genericContext.genericContents.contentsList = localContentsList;
4261 genericContext.genericContents.nbContents = 1;
4262 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4263
4264 // Then the tag/value pairs
4265 STARTING_CONTENT.type = TAG_VALUE_LIST;
4266 memcpy(
4267 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4268
4269 // compute number of pages & fill navigation structure
4270 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4271 &genericContext.genericContents,
4272 0,
4273 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4274 prepareNavInfo(true,
4276 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4277 // if the operation is skippable
4278 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4279 navInfo.progressIndicator = false;
4280 navInfo.skipText = "Skip";
4281 navInfo.skipToken = SKIP_TOKEN;
4282 }
4283
4284 displayGenericContextPage(0, true);
4285}
4286
4298 nbgl_choiceCallback_t choiceCallback)
4299{
4300 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4301}
4302
4311void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4312 nbgl_choiceCallback_t choiceCallback)
4313{
4314 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4315 memset(&genericContext, 0, sizeof(genericContext));
4316
4317 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4318
4319 // memorize context
4320 onChoice = bundleNavReviewStreamingChoice;
4321 navType = STREAMING_NAV;
4322 pageTitle = NULL;
4323
4324 genericContext.genericContents.contentsList = localContentsList;
4325 genericContext.genericContents.nbContents = 1;
4326 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4327
4328 // Eventually the long press page
4329 STARTING_CONTENT.type = INFO_LONG_PRESS;
4330 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4331 &STARTING_CONTENT.content.infoLongPress,
4332 bundleNavContext.reviewStreaming.icon,
4333 finishTitle);
4334
4335 // compute number of pages & fill navigation structure
4336 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4337 &genericContext.genericContents,
4338 0,
4339 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4340 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4341
4342 displayGenericContextPage(0, true);
4343}
4344
4349void nbgl_useCaseAddressConfirmationExt(const char *address,
4350 nbgl_choiceCallback_t callback,
4351 const nbgl_contentTagValueList_t *tagValueList)
4352{
4353 reset_callbacks();
4354 memset(&genericContext, 0, sizeof(genericContext));
4355 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4356
4357 // save context
4358 onChoice = callback;
4359 navType = GENERIC_NAV;
4360 pageTitle = NULL;
4361
4362 genericContext.genericContents.contentsList = localContentsList;
4363 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4364 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4365 prepareAddressConfirmationPages(
4366 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4367
4368 // fill navigation structure, common to all pages
4369 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4370
4371 prepareNavInfo(true, nbPages, "Cancel");
4372
4373#ifdef HAVE_PIEZO_SOUND
4374 // Play notification sound
4375 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4376#endif // HAVE_PIEZO_SOUND
4377
4378 displayGenericContextPage(0, true);
4379}
4380
4397void nbgl_useCaseAddressReview(const char *address,
4398 const nbgl_contentTagValueList_t *additionalTagValueList,
4399 const nbgl_icon_details_t *icon,
4400 const char *reviewTitle,
4401 const char *reviewSubTitle,
4402 nbgl_choiceCallback_t choiceCallback)
4403{
4404 reset_callbacks();
4405 memset(&genericContext, 0, sizeof(genericContext));
4406 // release a potential modal
4407 if (addressConfirmationContext.modalLayout) {
4408 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4409 }
4410 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4411
4412 // save context
4413 onChoice = choiceCallback;
4414 navType = GENERIC_NAV;
4415 pageTitle = NULL;
4416 bundleNavContext.review.operationType = TYPE_OPERATION;
4417
4418 genericContext.genericContents.contentsList = localContentsList;
4419 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4420
4421 // First a centered info
4422 STARTING_CONTENT.type = EXTENDED_CENTER;
4423 prepareReviewFirstPage(
4424 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4425 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4426
4427 // Then the address confirmation pages
4428 prepareAddressConfirmationPages(
4429 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4430
4431 // fill navigation structure, common to all pages
4432 genericContext.genericContents.nbContents
4433 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4434 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4435
4436 prepareNavInfo(true, nbPages, "Cancel");
4437
4438#ifdef HAVE_PIEZO_SOUND
4439 // Play notification sound
4440 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4441#endif // HAVE_PIEZO_SOUND
4442
4443 displayGenericContextPage(0, true);
4444}
4445
4454void nbgl_useCaseSpinner(const char *text)
4455{
4456 // if the previous Use Case was not Spinner, fresh start
4457 if (genericContext.type != USE_CASE_SPINNER) {
4458 memset(&genericContext, 0, sizeof(genericContext));
4459 genericContext.type = USE_CASE_SPINNER;
4460 nbgl_layoutDescription_t layoutDescription = {0};
4461
4462 layoutDescription.withLeftBorder = true;
4463
4464 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4465
4467 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4468
4469 nbgl_layoutDraw(genericContext.backgroundLayout);
4471 }
4472 else {
4473 // otherwise increment spinner
4474 genericContext.spinnerPosition++;
4475 // there are only NB_SPINNER_POSITIONSpositions
4476 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4477 genericContext.spinnerPosition = 0;
4478 }
4479 int ret = nbgl_layoutUpdateSpinner(
4480 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4481 if (ret == 1) {
4483 }
4484 else if (ret == 2) {
4486 }
4487 }
4488}
4489
4490#ifdef NBGL_KEYPAD
4510void nbgl_useCaseKeypadDigits(const char *title,
4511 uint8_t minDigits,
4512 uint8_t maxDigits,
4513 uint8_t backToken,
4514 bool shuffled,
4515 tune_index_e tuneId,
4516 nbgl_pinValidCallback_t validatePinCallback,
4517 nbgl_layoutTouchCallback_t actionCallback)
4518{
4519 keypadGenericUseCase(title,
4520 minDigits,
4521 maxDigits,
4522 backToken,
4523 shuffled,
4524 false,
4525 tuneId,
4526 validatePinCallback,
4527 actionCallback);
4528}
4548void nbgl_useCaseKeypadPIN(const char *title,
4549 uint8_t minDigits,
4550 uint8_t maxDigits,
4551 uint8_t backToken,
4552 bool shuffled,
4553 tune_index_e tuneId,
4554 nbgl_pinValidCallback_t validatePinCallback,
4555 nbgl_layoutTouchCallback_t actionCallback)
4556{
4557 keypadGenericUseCase(title,
4558 minDigits,
4559 maxDigits,
4560 backToken,
4561 shuffled,
4562 true,
4563 tuneId,
4564 validatePinCallback,
4565 actionCallback);
4566}
4567#endif // NBGL_KEYPAD
4568
4569#endif // HAVE_SE_TOUCH
4570#endif // NBGL_USE_CASE
nbgl_contentTagValue_t *(* nbgl_contentTagValueCallback_t)(uint8_t pairIndex)
prototype of tag/value pair retrieval callback
@ ICON_ILLUSTRATION
simple icon
@ LARGE_CASE_GRAY_INFO
@ LARGE_CASE_INFO
text in BLACK and large case (INTER 32px), subText in black in Inter24px
@ INFO_LONG_PRESS
a centered info and a long press button
@ EXTENDED_CENTER
a centered content and a possible tip-box
@ CHOICES_LIST
list of choices through radio buttons
@ CENTERED_INFO
a centered info
@ SWITCHES_LIST
list of switches with descriptions
@ TAG_VALUE_DETAILS
a tag/value pair and a small button to get details.
@ INFOS_LIST
list of infos with titles
@ TAG_VALUE_CONFIRM
tag/value pairs and a black button/footer to confirm/cancel.
@ TAG_VALUE_LIST
list of tag/value pairs
@ BARS_LIST
list of touchable bars (with > on the right to go to sub-pages)
@ INFO_BUTTON
a centered info and a simple black button
@ INFO_LIST_ALIAS
alias is list of infos
@ TAG_VALUE_LIST_ALIAS
@ ENS_ALIAS
alias comes from ENS
@ ADDRESS_BOOK_ALIAS
alias comes from Address Book
@ QR_CODE_ALIAS
alias is an address to be displayed as a QR Code
void(* nbgl_contentActionCallback_t)(int token, uint8_t index, int page)
prototype of function to be called when an action on a content object occurs
debug traces management
#define LOG_DEBUG(__logger,...)
Definition nbgl_debug.h:86
@ USE_CASE_LOGGER
Definition nbgl_debug.h:35
bool nbgl_getTextMaxLenInNbLines(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, uint16_t maxNbLines, uint16_t *len, bool wrapping)
compute the len of the given text (in bytes) fitting in the given maximum nb lines,...
Definition nbgl_fonts.c:566
nbgl_font_id_e
Definition nbgl_fonts.h:143
void nbgl_textReduceOnNbLines(nbgl_font_id_e fontId, const char *origText, uint16_t maxWidth, uint8_t nbLines, char *reducedText, uint16_t reducedTextLen)
Create a reduced version of given ASCII text to wrap it on the given max width (in pixels),...
uint16_t nbgl_getTextHeightInWidth(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, bool wrapping)
return the height of the given multiline text, with the given font.
uint16_t nbgl_getTextNbLinesInWidth(nbgl_font_id_e fontId, const char *text, uint16_t maxWidth, bool wrapping)
compute the number of lines of the given text fitting in the given maxWidth
Definition nbgl_fonts.c:725
void(* nbgl_layoutTouchCallback_t)(int token, uint8_t index)
prototype of function to be called when an object is touched
int nbgl_layoutAddContentCenter(nbgl_layout_t *layout, const nbgl_contentCenter_t *info)
Creates an area on the center of the main panel, with a possible icon, and possible texts under it.
int nbgl_layoutAddTextContent(nbgl_layout_t *layout, const char *title, const char *description, const char *info)
Creates in the main container three text areas:
int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, uint8_t index, bool enableValidate, bool enableBackspace, bool enableDigits)
Updates an existing keypad on bottom of the screen, with the given configuration.
int nbgl_layoutAddSeparationLine(nbgl_layout_t *layout)
adds a separation line on bottom of the last added item
int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info)
Creates an area on the center of the main panel, with a QRCode, a possible text in black (bold) under...
int nbgl_layoutDraw(nbgl_layout_t *layout)
Applies given layout. The screen will be redrawn.
int nbgl_layoutAddTouchableBar(nbgl_layout_t *layout, const nbgl_layoutBar_t *barLayout)
Creates a touchable bar in main panel.
@ WHITE_BACKGROUND
rounded bordered button, with text/icon in black, on white background
@ BLACK_BACKGROUND
rounded bordered button, with text/icon in white, on black background
int nbgl_layoutAddTopRightButton(nbgl_layout_t *layout, const nbgl_icon_details_t *icon, uint8_t token, tune_index_e tuneId)
Creates a Top-right button in the top right corner of the top panel.
#define AVAILABLE_WIDTH
void * nbgl_layout_t
type shared externally
@ HEADER_EMPTY
empty space, to have a better vertical centering of centered info
@ HEADER_BACK_AND_TEXT
back key and optional text
int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, const char *title, bool hidden, uint8_t nbDigits, const char *text)
Adds an area with a title and a placeholder for hidden digits on top of a keypad, to represent the en...
#define NBGL_INVALID_TOKEN
Definition nbgl_layout.h:30
int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, bool hidden, uint8_t nbActiveDigits, const char *text)
Updates an existing set of hidden digits, with the given configuration.
nbgl_layout_t * nbgl_layoutGet(const nbgl_layoutDescription_t *description)
returns a layout of the given type. The layout is reset
int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_t *footerDesc)
Creates a touchable area at the footer of the screen, containing various controls,...
@ ROUNDED_AND_FOOTER_STYLE
A black background button on top of a footer.
int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceButtons_t *info)
Creates two buttons to make a choice. Both buttons are mandatory. Both buttons are full width,...
int nbgl_layoutAddSpinner(nbgl_layout_t *layout, const char *text, const char *subText, uint8_t initPosition)
Creates a centered (vertically & horizontally) spinner with a text under it.
int nbgl_layoutUpdateSpinner(nbgl_layout_t *layout, const char *text, const char *subText, uint8_t position)
Update an existing spinner (must be the only object of the layout)
#define NBGL_NO_PROGRESS_INDICATOR
To be used when a control token shall not be used.
Definition nbgl_layout.h:27
int nbgl_layoutAddHeader(nbgl_layout_t *layout, const nbgl_layoutHeader_t *headerDesc)
Creates a touchable (or not) area at the header of the screen, containing various controls,...
@ FOOTER_EMPTY
empty space, to have a better vertical centering of centered info
int nbgl_layoutRelease(nbgl_layout_t *layout)
Release the layout obtained with nbgl_layoutGet()
#define EXIT_PAGE
Definition nbgl_layout.h:35
int nbgl_layoutAddFooter(nbgl_layout_t *layout, const char *text, uint8_t token, tune_index_e tuneId)
Creates a touchable text at the footer of the screen, separated with a thin line from the rest of the...
int nbgl_layoutAddKeypad(nbgl_layout_t *layout, keyboardCallback_t callback, bool shuffled)
Adds a keypad on bottom of the screen, with the associated callback.
#define NB_SPINNER_POSITIONS
Definition nbgl_obj.h:264
void nbgl_refresh(void)
This functions refreshes the actual screen on display with what has changed since the last refresh.
Definition nbgl_obj.c:1669
#define KEYPAD_MAX_DIGITS
Definition nbgl_obj.h:67
void nbgl_refreshSpecial(nbgl_refresh_mode_t mode)
This functions refreshes the actual screen on display with what has changed since the last refresh,...
Definition nbgl_obj.c:1679
#define BACKSPACE_KEY
Definition nbgl_obj.h:26
void nbgl_refreshSpecialWithPostRefresh(nbgl_refresh_mode_t mode, nbgl_post_refresh_t post_refresh)
Definition nbgl_obj.c:1695
#define VALIDATE_KEY
Definition nbgl_obj.h:27
nbgl_page_t * nbgl_pageDrawGenericContent(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageNavigationInfo_t *nav, nbgl_pageContent_t *content)
draw a generic content page, with the given content, and if nav parameter is not NULL,...
Definition nbgl_page.c:612
nbgl_page_t * nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_screenTickerConfiguration_t *ticker, const nbgl_pageInfoDescription_t *info)
draw a page with a centered info (icon and/or texts) with a touchable footer, in a potential "tapable...
Definition nbgl_page.c:325
void * nbgl_page_t
type shared externally
Definition nbgl_page.h:81
@ NAV_WITH_BUTTONS
move forward and backward with buttons in bottom nav bar
Definition nbgl_page.h:89
nbgl_page_t * nbgl_pageDrawLedgerInfo(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_screenTickerConfiguration_t *ticker, const char *text, int tapActionToken)
draw a page with a centered text in large case, with a round check icon
Definition nbgl_page.c:256
nbgl_page_t * nbgl_pageDrawConfirmation(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageConfirmationDescription_t *info)
draw a confirmation page, with a centered info (icon and/or text), a button to confirm and a footer t...
Definition nbgl_page.c:442
int nbgl_pageRelease(nbgl_page_t *)
Release the page obtained with any of the nbgl_pageDrawXXX() functions.
Definition nbgl_page.c:625
nbgl_page_t * nbgl_pageDrawGenericContentExt(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageNavigationInfo_t *nav, nbgl_pageContent_t *content, bool modal)
draw a generic content page, with the given content, and if nav parameter is not NULL,...
Definition nbgl_page.c:483
@ QUIT_APP_TEXT
A full width button with "Quit app" text (only for bottom button)
Definition nbgl_page.h:40
@ INFO_ICON
info (i) icon in the button.
Definition nbgl_page.h:39
@ NO_BUTTON_STYLE
no button.
Definition nbgl_page.h:36
@ SETTINGS_ICON
settings (wheel) icon in the button.
Definition nbgl_page.h:37
struct PACKED__ nbgl_screenTickerConfiguration_s nbgl_screenTickerConfiguration_t
struct to configure a screen layer
void nbgl_screenRedraw(void)
This function redraws the whole screen on top of stack and its children.
Definition nbgl_screen.c:66
@ POST_REFRESH_FORCE_POWER_ON
Force screen power on after refresh.
Definition nbgl_types.h:354
#define MIN(x, y)
Definition nbgl_types.h:118
struct PACKED__ nbgl_icon_details_s nbgl_icon_details_t
Represents all information about an icon.
#define MAX(x, y)
Definition nbgl_types.h:121
nbgl_refresh_mode_t
different modes of refresh for nbgl_refreshSpecial()
Definition nbgl_types.h:326
@ FULL_COLOR_CLEAN_REFRESH
to be used for lock screen display (cleaner but longer refresh)
Definition nbgl_types.h:329
@ BLACK_AND_WHITE_FAST_REFRESH
to be used for pure B&W area, when contrast is not priority
Definition nbgl_types.h:331
@ FULL_COLOR_PARTIAL_REFRESH
to be used for small partial refresh (radio buttons, switches)
Definition nbgl_types.h:328
@ FULL_COLOR_REFRESH
to be used for normal refresh
Definition nbgl_types.h:327
API of the Advanced BOLOS Graphical Library, for typical application use-cases.
DEPRECATED void nbgl_useCaseHome(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, bool withSettings, nbgl_callback_t topRightCallback, nbgl_callback_t quitCallback)
#define NB_MAX_LINES_IN_REVIEW
maximum number of lines for value field in review pages
@ NO_TYPE_WARNING
Invalid type (to use for bars leading to nothing)
@ CENTERED_INFO_WARNING
Centered info.
@ QRCODE_WARNING
QR Code.
@ BAR_LIST_WARNING
list of touchable bars, to display sub-pages
DEPRECATED void nbgl_useCaseSettings(const char *settingsTitle, uint8_t initPage, uint8_t nbPages, bool touchableTitle, nbgl_callback_t quitCallback, nbgl_navCallback_t navCallback, nbgl_layoutTouchCallback_t controlsCallback)
uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs, const nbgl_contentTagValueList_t *tagValueList, uint8_t startIndex, bool isSkippable, bool *requireSpecificDisplay)
void(* nbgl_callback_t)(void)
prototype of generic callback function
#define SKIPPABLE_OPERATION
This is to use in nbgl_operationType_t when the operation is skippable. This is used.
void nbgl_useCaseGenericSettings(const char *appName, uint8_t initPage, const nbgl_genericContents_t *settingContents, const nbgl_contentInfoList_t *infosList, nbgl_callback_t quitCallback)
void nbgl_useCaseKeypadPIN(const char *title, uint8_t minDigits, uint8_t maxDigits, uint8_t backToken, bool shuffled, tune_index_e tuneId, nbgl_pinValidCallback_t validatePinCallback, nbgl_layoutTouchCallback_t actionCallback)
uint32_t nbgl_operationType_t
This mask is used to describe the type of operation to review with additional options It is a mask of...
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_useCaseAction(const nbgl_icon_details_t *icon, const char *message, const char *actionText, nbgl_callback_t callback)
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)
DEPRECATED void nbgl_useCaseRegularReview(uint8_t initPage, uint8_t nbPages, const char *rejectText, nbgl_layoutTouchCallback_t buttonCallback, nbgl_navCallback_t navCallback, nbgl_choiceCallback_t choiceCallback)
void(* nbgl_pinValidCallback_t)(const uint8_t *pin, uint8_t pinLen)
prototype of pin validation callback function
void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseHomeAndSettings(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, nbgl_callback_t quitCallback)
void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList, const nbgl_pageInfoLongPress_t *infoLongPress, const char *rejectText, nbgl_choiceCallback_t callback)
uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos, const nbgl_contentInfoList_t *infosList, uint8_t startIndex, bool withNav)
void(* nbgl_choiceCallback_t)(bool confirm)
prototype of choice callback function
uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars, const nbgl_contentBarsList_t *barsList, uint8_t startIndex, bool withNav)
void nbgl_useCaseNavigableContent(const char *title, uint8_t initPage, uint8_t nbPages, nbgl_callback_t quitCallback, nbgl_navCallback_t navCallback, nbgl_layoutTouchCallback_t controlsCallback)
#define INFOS_AREA_HEIGHT
height available for infos pairs display
@ STRONG_HOME_ACTION
Black button, implicating the main action of the App.
void nbgl_useCaseReviewStreamingFinish(const char *finishTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseChoiceWithDetails(const nbgl_icon_details_t *icon, const char *message, const char *subMessage, const char *confirmText, const char *cancelText, nbgl_warningDetails_t *details, nbgl_choiceCallback_t callback)
void nbgl_useCaseSpinner(const char *text)
void nbgl_useCaseConfirm(const char *message, const char *subMessage, const char *confirmText, const char *rejectText, nbgl_callback_t callback)
#define NB_MAX_LINES_IN_DETAILS
maximum number of lines for value field in details pages
void nbgl_useCaseReviewStart(const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *rejectText, nbgl_callback_t continueCallback, nbgl_callback_t rejectCallback)
DEPRECATED void nbgl_useCaseHomeExt(const char *appName, const nbgl_icon_details_t *appIcon, const char *tagline, bool withSettings, const char *actionButtonText, nbgl_callback_t actionCallback, nbgl_callback_t topRightCallback, nbgl_callback_t quitCallback)
void nbgl_useCaseKeypadDigits(const char *title, uint8_t minDigits, uint8_t maxDigits, uint8_t backToken, bool shuffled, tune_index_e tuneId, nbgl_pinValidCallback_t validatePinCallback, nbgl_layoutTouchCallback_t actionCallback)
#define APP_DESCRIPTION_MAX_LEN
Length of buffer used for the default Home tagline.
void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
@ W3C_THREAT_DETECTED_WARN
Web3 Checks: Threat detected (see reportRisk field)
@ W3C_ISSUE_WARN
Web3 Checks issue (not available)
@ BLIND_SIGNING_WARN
Blind signing.
@ W3C_NO_THREAT_WARN
Web3 Checks: No Threat detected.
@ W3C_RISK_DETECTED_WARN
Web3 Checks: Risk detected (see reportRisk field)
@ NB_WARNING_TYPES
DEPRECATED void nbgl_useCaseAddressConfirmationExt(const char *address, nbgl_choiceCallback_t callback, const nbgl_contentTagValueList_t *tagValueList)
void nbgl_useCaseGenericConfiguration(const char *title, uint8_t initPage, const nbgl_genericContents_t *contents, nbgl_callback_t quitCallback)
#define INIT_HOME_PAGE
Value to pass to nbgl_useCaseHomeAndSettings() initSettingPage parameter to initialize the use case o...
void nbgl_useCaseReviewStreamingContinueExt(const nbgl_contentTagValueList_t *tagValueList, nbgl_choiceCallback_t choiceCallback, nbgl_callback_t skipCallback)
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 *tipBox, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseChoice(const nbgl_icon_details_t *icon, const char *message, const char *subMessage, const char *confirmText, const char *rejectString, nbgl_choiceCallback_t callback)
#define TAG_VALUE_AREA_HEIGHT
height available for tag/value pairs display
#define BLIND_OPERATION
This is to use in nbgl_operationType_t when the operation is "blind" This is used to indicate a warni...
#define TAGLINE_PART1
Default strings used in the Home tagline.
void nbgl_useCaseGenericReview(const nbgl_genericContents_t *contents, const char *rejectText, nbgl_callback_t rejectCallback)
#define STATUS_SCREEN_DURATION
#define TAGLINE_PART2
void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList, const nbgl_pageInfoLongPress_t *infoLongPress, const char *rejectText, nbgl_choiceCallback_t callback)
void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseReviewStreamingContinue(const nbgl_contentTagValueList_t *tagValueList, 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)
uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices, const nbgl_contentRadioChoice_t *choicesList, uint8_t startIndex, bool withNav)
#define REAL_TYPE_MASK
This is the mask to apply on nbgl_operationType_t to get the real type provided by app.
void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType, nbgl_callback_t quitCallback)
#define LAST_PAGE_FOR_REVIEW
value of page parameter used with navigation callback when "skip" button is touched,...
nbgl_reviewStatusType_t
The different types of review status.
@ STATUS_TYPE_TRANSACTION_REJECTED
@ STATUS_TYPE_ADDRESS_REJECTED
@ STATUS_TYPE_TRANSACTION_SIGNED
@ STATUS_TYPE_OPERATION_REJECTED
@ STATUS_TYPE_OPERATION_SIGNED
@ STATUS_TYPE_ADDRESS_VERIFIED
@ STATUS_TYPE_MESSAGE_SIGNED
@ STATUS_TYPE_MESSAGE_REJECTED
bool(* nbgl_navCallback_t)(uint8_t page, nbgl_pageContent_t *content)
prototype of navigation callback function
#define MAX_APP_NAME_FOR_SDK_TAGLINE
Max supported length of appName used for the default Home tagline.
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)
@ TYPE_MESSAGE
@ TYPE_TRANSACTION
For operations transferring a coin or taken from an account to another.
@ TYPE_OPERATION
For other types of operation (generic type)
void nbgl_useCaseAdvancedReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const nbgl_warning_t *warning, nbgl_choiceCallback_t choiceCallback)
void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, const char *reviewTitle, const char *reviewSubTitle, const char *finishTitle, const nbgl_tipBox_t *tipBox, const nbgl_warning_t *warning, nbgl_choiceCallback_t choiceCallback)
uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches, const nbgl_contentSwitchesList_t *switchesList, uint8_t startIndex, bool withNav)
This structure contains data to build a BARS_LIST content.
const uint8_t * tokens
array of tokens, one for each bar (nbBars items)
const char *const * barTexts
array of texts for each bar (nbBars items, in black/bold)
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when a bar is touched
uint8_t nbBars
number of elements in barTexts and tokens array
This structure contains info to build a centered (vertically and horizontally) area,...
uint16_t iconHug
vertical margin to apply on top and bottom of the icon
const nbgl_icon_details_t * icon
the icon (can be null)
const char * title
title in black large (can be null)
const char * description
description in black small regular case (can be null)
const char * subText
sub-text in dark gray regular small case
bool padding
if true, apply a padding of 40px at the bottom
const char * smallTitle
sub-title in black small bold case (can be null)
nbgl_contentIllustrationType_t illustrType
const char * text2
second text (can be null)
const char * text1
first text (can be null)
nbgl_contentCenteredInfoStyle_t style
style to apply to this info
int16_t offsetY
vertical shift to apply to this info (if >0, shift to bottom)
const char * text3
third text (can be null)
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
nbgl_contentCenter_t contentCenter
centered content (icon + text(s))
This structure contains data to build a centered info + simple black button content.
const char * buttonText
text of the long press button
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
const char * text
centered text in large case
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button is touched
This structure contains data to build a INFOS_LIST content.
const char *const * infoContents
array of contents of infos (in black)
const char *const * infoTypes
array of types of infos (in black/bold)
const nbgl_contentValueExt_t * infoExtensions
uint8_t nbInfos
number of elements in infoTypes and infoContents array
This structure contains data to build a centered info + long press button content.
const char * longPressText
text of the long press button
const nbgl_icon_details_t * icon
a buffer containing the 1BPP icon
const char * text
centered text in large case
This structure contains a list of names to build a list of radio buttons (on the right part of screen...
uint8_t initChoice
index of the current choice
const char *const * names
array of strings giving the choices (nbChoices)
uint8_t nbChoices
number of choices
This structure contains info to build a switch (on the right) with a description (on the left),...
const char * text
main text for the switch
const char * subText
description under main text (NULL terminated, single line, may be null)
This structure contains [item,value] pair(s) and info about a potential "details" button,...
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when details button is touched
const char * confirmationText
text of the confirmation button, if NULL "It matches" is used
uint8_t confirmationToken
the token used as argument of the onActionCallback
nbgl_contentTagValueList_t tagValueList
list of tag/value pairs
const char * detailsButtonText
this text is used for "details" button (if NULL, no button)
const nbgl_icon_details_t * detailsButtonIcon
icon to use in details button
const nbgl_icon_details_t * detailsButtonIcon
icon to use in details button
const char * detailsButtonText
this text is used for "details" button
nbgl_contentTagValueList_t tagValueList
list of tag/value pairs
This structure contains a list of [tag,value] pairs.
const nbgl_contentTagValue_t * pairs
array of [tag,value] pairs (nbPairs items). If NULL, callback is used instead
nbgl_contentTagValueCallback_t callback
function to call to retrieve a given pair
bool wrapping
if set to true, value text will be wrapped on ' ' to avoid cutting words
uint8_t startIndex
index of the first pair to get with callback
nbgl_contentActionCallback_t actionCallback
called when a valueIcon is touched on a given pair
This structure contains a [tag,value] pair and possible extensions.
const nbgl_contentValueExt_t * extension
if not NULL, gives additional info on value field
const nbgl_icon_details_t * valueIcon
int8_t centeredInfo
if set to 1, the tag will be displayed as a centered info
const char * value
string giving the value name
const char * item
string giving the tag name
This structure contains additions to a tag/value pair, to be able to build a screen to display these ...
const char * fullValue
full string of the value when used as an alias
nbgl_contentValueAliasType_t aliasType
type of alias
const struct nbgl_contentTagValueList_s * tagValuelist
if aliasType is TAG_VALUE_LIST_ALIAS
const char * backText
used as title of the popping page, if not NULL, otherwise "item" is used
const struct nbgl_contentInfoList_s * infolist
if aliasType is INFO_LIST_ALIAS
This structure contains data to build a content.
nbgl_content_u content
nbgl_contentActionCallback_t contentActionCallback
callback to be called when an action on an object occurs
nbgl_contentType_t type
type of page content in the content union
const struct nbgl_genericDetails_s * details
array of nbBars structures giving what to display when each bar is touched.
uint8_t nbBars
number of touchable bars
const nbgl_icon_details_t ** icons
array of icons for each bar (nbBars items)
const char *const * subTexts
array of texts for each bar (nbBars items, in black)
const char *const * texts
array of texts for each bar (nbBars items, in black/bold)
uint8_t nbContents
number of contents
const nbgl_content_t * contentsList
array of nbgl_content_t (nbContents items).
nbgl_contentCallback_t contentGetterCallback
function to call to retrieve a given content
The necessary parameters to build the page(s) displayed when the top-right button is touched in intro...
const char * title
text of the page (used to go back)
nbgl_genericBarList_t barList
touchable bars list, if type == BAR_LIST_WARNING
nbgl_layoutQRCode_t qrCode
QR code, if type == QRCODE_WARNING.
nbgl_genericDetailsType_t type
type of content in the page, determining what to use in the following union
nbgl_contentCenter_t centeredInfo
centered info, if type == CENTERED_INFO_WARNING
Structure describing the action button in Home Screen.
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
const char * text
text to use in action button in Home page
nbgl_homeActionStyle_t style
style of action button
This structure contains info to build a clickable "bar" with a text and an icon.
bool inactive
if set to true, the bar is grayed-out and cannot be touched
const char * text
text (can be NULL)
uint8_t token
the token that will be used as argument of the callback
bool large
set to true only for the main level of OS settings
const char * subText
sub text (can be NULL)
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played
const nbgl_icon_details_t * iconLeft
a buffer containing the 1BPP icon for icon on left (can be NULL)
const nbgl_icon_details_t * iconRight
This structure contains info to build a pair of buttons, one on top of the other.
const char * bottomText
bottom-button text (index 1)
Structure containing all information when creating a layout. This structure must be passed as argumen...
nbgl_screenTickerConfiguration_t ticker
const char * tapActionText
Light gray text used when main container is "tapable".
nbgl_layoutTouchCallback_t onActionCallback
the callback to be called on any action on the layout
This structure contains info to build an extended footer.
struct nbgl_layoutFooter_t::@19::@21 emptySpace
if type is FOOTER_EMPTY
nbgl_layoutFooterType_t type
type of footer
This structure contains info to build a header.
nbgl_layoutHeaderType_t type
type of header
bool separationLine
if true, a separation line is added at the bottom of this control
const char * text
can be NULL if no text
struct nbgl_layoutHeader_t::@11::@14 backAndText
if type is HEADER_BACK_ICON_AND_TEXT or HEADER_BACK_AND_TEXT
This structure contains info to build a centered (vertically and horizontally) area,...
const char * text2
second text (can be null)
const char * url
URL for QR code.
Structure containing all specific information when creating a confirmation page.
Definition nbgl_page.h:149
const char * cancelText
the text used for cancel action, if NULL a simple X button is used
Definition nbgl_page.h:152
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button is pressed
Definition nbgl_page.h:157
This structure contains data to build a page in multi-pages mode (nbgl_pageDrawGenericContent)
Definition nbgl_flow.h:58
nbgl_contentTagValueDetails_t tagValueDetails
TAG_VALUE_DETAILS type
Definition nbgl_page.h:68
const char * title
text for the title of the page (if NULL, no title)
Definition nbgl_page.h:53
uint8_t topRightToken
token used when top-right button (if not NULL) is touched
Definition nbgl_page.h:58
nbgl_contentInfoLongPress_t infoLongPress
INFO_LONG_PRESS type
Definition nbgl_page.h:65
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type
Definition nbgl_flow.h:66
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
Definition nbgl_flow.h:64
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when title is touched
Definition nbgl_page.h:57
nbgl_contentBarsList_t barsList
BARS_LIST type
Definition nbgl_flow.h:67
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
Definition nbgl_flow.h:62
nbgl_contentInfoList_t infosList
INFOS_LIST type
Definition nbgl_flow.h:65
nbgl_contentTagValueList_t tagValueList
TAG_VALUE_LIST type
Definition nbgl_flow.h:63
const nbgl_icon_details_t * topRightIcon
Definition nbgl_page.h:59
nbgl_contentType_t type
type of page content in the following union
Definition nbgl_flow.h:59
nbgl_contentCenteredInfo_t centeredInfo
CENTERED_INFO type
Definition nbgl_flow.h:61
nbgl_contentTagValueConfirm_t tagValueConfirm
TAG_VALUE_CONFIRM type
Definition nbgl_page.h:69
bool isTouchableTitle
if set to true, the title is preceded by <- arrow to go back
Definition nbgl_page.h:54
nbgl_contentExtendedCenter_t extendedCenter
EXTENDED_CENTER type
Definition nbgl_page.h:64
Structure containing all specific information when creating an information page.
Definition nbgl_page.h:185
nbgl_layoutButtonStyle_t actionButtonStyle
style of "action" button
Definition nbgl_page.h:202
const char * actionButtonText
if not NULL an "action" button is set under the centered info
Definition nbgl_page.h:200
const nbgl_icon_details_t * actionButtonIcon
potential icon of "action" button
Definition nbgl_page.h:201
nbgl_pageButtonStyle_t bottomButtonStyle
style to apply to the Bottom button
Definition nbgl_page.h:188
nbgl_layoutCenteredInfo_t centeredInfo
description of the centered info to be used
Definition nbgl_page.h:186
Structure containing all specific information when creating a multi-screens page.
Definition nbgl_page.h:127
uint8_t nbPages
the number of pages to display (if <2, no navigation bar)
Definition nbgl_page.h:129
uint8_t quitToken
the token used as argument of the actionCallback when the footer is touched
Definition nbgl_page.h:131
uint8_t skipToken
if skipText is NULL the token used when right part of footer is touched
Definition nbgl_page.h:139
nbgl_pageNavigationType_t navType
Definition nbgl_page.h:132
uint8_t activePage
the index of the page to display at start-up
Definition nbgl_page.h:128
bool progressIndicator
if set to true, display a progress indicator on top of the page
Definition nbgl_page.h:134
nbgl_pageNavWithButtons_t navWithButtons
structure used when navigation with buttons
Definition nbgl_page.h:142
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when next or back is pressed
Definition nbgl_page.h:136
bool visiblePageIndicator
if set to true, the page indicator will be visible in navigation
Definition nbgl_page.h:116
const char * quitText
the text displayed in footer (on the left), used to quit (only on Flex)
Definition nbgl_page.h:120
bool backButton
if set to true, a back button (<-) is displayed in the nav bar
Definition nbgl_page.h:114
This structure contains data to build a SWITCHES_LIST content.
uint8_t nbSwitches
number of elements in switches and tokens array
const nbgl_contentSwitch_t * switches
array of switches (nbSwitches items)
The necessary parameters to build a tip-box in first review page and the modal if this tip box is tou...
const char * modalTitle
title given to modal window displayed when tip-box is touched
nbgl_contentInfoList_t infos
infos pairs displayed in modal, if type is INFOS_LIST.
const char * text
text of the tip-box
const nbgl_icon_details_t * icon
icon of the tip-box
The necessary parameters to build a warning page preceding a review. One can either use predefinedSet...
const nbgl_warningDetails_t * introDetails
const nbgl_warningDetails_t * reviewDetails
uint32_t predefinedSet
Union of the different type of contents.
nbgl_contentInfoList_t infosList
INFOS_LIST type
nbgl_contentInfoLongPress_t infoLongPress
INFO_LONG_PRESS type
nbgl_contentTagValueConfirm_t tagValueConfirm
TAG_VALUE_CONFIRM type
nbgl_contentTagValueList_t tagValueList
TAG_VALUE_LIST type
nbgl_contentCenteredInfo_t centeredInfo
CENTERED_INFO type
nbgl_contentBarsList_t barsList
BARS_LIST type
nbgl_contentExtendedCenter_t extendedCenter
EXTENDED_CENTER type
nbgl_contentSwitchesList_t switchesList
SWITCHES_LIST type
nbgl_contentInfoButton_t infoButton
INFO_BUTTON type
nbgl_contentRadioChoice_t choicesList
CHOICES_LIST type