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 nbgl_tipBox_t tipBox;
213} GenericContext_t;
214
215typedef struct {
216 const char *appName;
217 const nbgl_icon_details_t *appIcon;
218 const char *tagline;
219 const nbgl_genericContents_t *settingContents;
220 const nbgl_contentInfoList_t *infosList;
221 nbgl_homeAction_t homeAction;
222 nbgl_callback_t quitCallback;
223} nbgl_homeAndSettingsContext_t;
224
225typedef struct {
226 nbgl_operationType_t operationType;
227 nbgl_choiceCallback_t choiceCallback;
228} nbgl_reviewContext_t;
229
230typedef struct {
231 nbgl_operationType_t operationType;
232 nbgl_choiceCallback_t choiceCallback;
233 nbgl_callback_t skipCallback;
234 const nbgl_icon_details_t *icon;
235 uint8_t stepPageNb;
236} nbgl_reviewStreamingContext_t;
237
238typedef union {
239 nbgl_homeAndSettingsContext_t homeAndSettings;
240 nbgl_reviewContext_t review;
241 nbgl_reviewStreamingContext_t reviewStreaming;
242} nbgl_BundleNavContext_t;
243
244typedef struct {
245 const nbgl_icon_details_t *icon;
246 const char *text;
247 const char *subText;
248} SecurityReportItem_t;
249
250/**********************
251 * STATIC VARIABLES
252 **********************/
253
254// char buffers to build some strings
255static char tmpString[W3C_DESCRIPTION_MAX_LEN];
256
257// multi-purposes callbacks
258static nbgl_callback_t onQuit;
259static nbgl_callback_t onContinue;
260static nbgl_callback_t onAction;
261static nbgl_navCallback_t onNav;
262static nbgl_layoutTouchCallback_t onControls;
263static nbgl_contentActionCallback_t onContentAction;
264static nbgl_choiceCallback_t onChoice;
265static nbgl_callback_t onModalConfirm;
266#ifdef NBGL_KEYPAD
267static nbgl_pinValidCallback_t onValidatePin;
268#endif
269
270// contexts for background and modal pages
271static nbgl_page_t *pageContext;
272static nbgl_page_t *modalPageContext;
273
274// context for pages
275static const char *pageTitle;
276
277// context for tip-box
278#define activeTipBox genericContext.tipBox
279
280// context for navigation use case
281static nbgl_pageNavigationInfo_t navInfo;
282static bool forwardNavOnly;
283static NavType_t navType;
284
285static DetailsContext_t detailsContext;
286
287// multi-purpose context shared for non-concurrent usages
288static SharedContext_t sharedContext;
289
290// context for address review
291static AddressConfirmationContext_t addressConfirmationContext;
292
293// contexts for generic navigation
294static GenericContext_t genericContext;
295static nbgl_content_t
296 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
297static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
298static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
299
300// contexts for bundle navigation
301static nbgl_BundleNavContext_t bundleNavContext;
302
303// indexed by nbgl_contentType_t
304static const uint8_t nbMaxElementsPerContentType[] = {
305 1, // CENTERED_INFO
306 1, // EXTENDED_CENTER
307 1, // INFO_LONG_PRESS
308 1, // INFO_BUTTON
309 1, // TAG_VALUE_LIST (computed dynamically)
310 1, // TAG_VALUE_DETAILS
311 1, // TAG_VALUE_CONFIRM
312 3, // SWITCHES_LIST (computed dynamically)
313 3, // INFOS_LIST (computed dynamically)
314 5, // CHOICES_LIST (computed dynamically)
315 5, // BARS_LIST (computed dynamically)
316};
317
318// clang-format off
319static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
321 .icon = &WARNING_ICON,
322 .text = "Blind signing required",
323 .subText = "This transaction's details are not fully verifiable. If "
324 "you sign, you could lose all your assets."
325 },
326 [W3C_ISSUE_WARN] = {
327 .icon = &WARNING_ICON,
328 .text = "Transaction Check unavailable",
329 .subText = NULL
330 },
332 .icon = &WARNING_ICON,
333 .text = "Risk detected",
334 .subText = "This transaction was scanned as risky by Web3 Checks."
335 },
337 .icon = &WARNING_ICON,
338 .text = "Critical threat",
339 .subText = "This transaction was scanned as malicious by Web3 Checks."
340 },
342 .icon = NULL,
343 .text = "No threat detected",
344 .subText = "Transaction Check didn't find any threat, but always "
345 "review transaction details carefully."
346 }
347};
348// clang-format on
349
350// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
351static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
352
353#ifdef NBGL_QRCODE
354/* buffer to store reduced address under QR Code */
355static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
356#endif // NBGL_QRCODE
357
358/**********************
359 * STATIC FUNCTIONS
360 **********************/
361static void displayReviewPage(uint8_t page, bool forceFullRefresh);
362static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
363static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
364static void displayFullValuePage(const char *backText,
365 const char *aliasText,
366 const nbgl_contentValueExt_t *extension);
367static void displayInfosListModal(const char *modalTitle,
368 const nbgl_contentInfoList_t *infos,
369 bool fromReview);
370static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
371static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
372static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
373static void pageCallback(int token, uint8_t index);
374#ifdef NBGL_QRCODE
375static void displayAddressQRCode(void);
376#endif // NBGL_QRCODE
377static void modalLayoutTouchCallback(int token, uint8_t index);
378static void displaySkipWarning(void);
379
380static void bundleNavStartHome(void);
381static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
382static void bundleNavStartSettings(void);
383
384static void bundleNavReviewStreamingChoice(bool confirm);
385static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token);
386static void displaySecurityReport(uint32_t set);
387static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
388static void displayInitialWarning(void);
389static void useCaseReview(nbgl_operationType_t operationType,
390 const nbgl_contentTagValueList_t *tagValueList,
391 const nbgl_icon_details_t *icon,
392 const char *reviewTitle,
393 const char *reviewSubTitle,
394 const char *finishTitle,
395 const nbgl_tipBox_t *tipBox,
396 nbgl_choiceCallback_t choiceCallback,
397 bool isLight,
398 bool playNotifSound);
399static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
400 const nbgl_icon_details_t *icon,
401 const char *reviewTitle,
402 const char *reviewSubTitle,
403 nbgl_choiceCallback_t choiceCallback,
404 bool playNotifSound);
405static void useCaseHomeExt(const char *appName,
406 const nbgl_icon_details_t *appIcon,
407 const char *tagline,
408 bool withSettings,
409 nbgl_homeAction_t *homeAction,
410 nbgl_callback_t topRightCallback,
411 nbgl_callback_t quitCallback);
412static void displayDetails(const char *tag, const char *value, bool wrapping);
413
414static void reset_callbacks_and_context(void)
415{
416 onQuit = NULL;
417 onContinue = NULL;
418 onAction = NULL;
419 onNav = NULL;
420 onControls = NULL;
421 onContentAction = NULL;
422 onChoice = NULL;
423#ifdef NBGL_KEYPAD
424 onValidatePin = NULL;
425#endif
426 memset(&genericContext, 0, sizeof(genericContext));
427}
428
429// Helper to set genericContext page info
430static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
431{
432 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
433
434 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
435 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
436 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
437 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
438}
439
440// Helper to get genericContext page info
441static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
442{
443 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
444 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
445 if (nbElements != NULL) {
446 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
447 }
448 if (flag != NULL) {
449 *flag = GET_PAGE_FLAG(pageData);
450 }
451}
452
453// Helper to set modalContext page info
454static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
455{
456 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
457
458 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
459 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
460 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
461 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
462}
463
464// Helper to get modalContext page info
465static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
466{
467 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
468 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
469 if (nbElements != NULL) {
470 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
471 }
472}
473
474// Simple helper to get the number of elements inside a nbgl_content_t
475static uint8_t getContentNbElement(const nbgl_content_t *content)
476{
477 switch (content->type) {
478 case TAG_VALUE_LIST:
479 return content->content.tagValueList.nbPairs;
482 case SWITCHES_LIST:
483 return content->content.switchesList.nbSwitches;
484 case INFOS_LIST:
485 return content->content.infosList.nbInfos;
486 case CHOICES_LIST:
487 return content->content.choicesList.nbChoices;
488 case BARS_LIST:
489 return content->content.barsList.nbBars;
490 default:
491 return 1;
492 }
493}
494
495// Helper to retrieve the content inside a nbgl_genericContents_t using
496// either the contentsList or using the contentGetterCallback
497static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
498 int8_t contentIdx,
499 nbgl_content_t *content)
500{
501 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
502 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
503 return NULL;
504 }
505
506 if (genericContents->callbackCallNeeded) {
507 // Retrieve content through callback, but first memset the content.
508 memset(content, 0, sizeof(nbgl_content_t));
509 genericContents->contentGetterCallback(contentIdx, content);
510 return content;
511 }
512 else {
513 // Retrieve content through list
514 return PIC(&genericContents->contentsList[contentIdx]);
515 }
516}
517
518static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
519{
520 memset(&navInfo, 0, sizeof(navInfo));
521
522 navInfo.nbPages = nbPages;
523 navInfo.tuneId = TUNE_TAP_CASUAL;
524 navInfo.progressIndicator = false;
525 navInfo.navType = NAV_WITH_BUTTONS;
526
527 if (isReview == false) {
528 navInfo.navWithButtons.navToken = NAV_TOKEN;
529 navInfo.navWithButtons.backButton = true;
530 }
531 else {
532 navInfo.quitToken = REJECT_TOKEN;
533 navInfo.navWithButtons.quitText = rejectText;
534 navInfo.navWithButtons.navToken = NAV_TOKEN;
536 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
537 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
538 }
539}
540
541static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
542 const nbgl_icon_details_t *icon,
543 const char *reviewTitle,
544 const char *reviewSubTitle)
545{
546 contentCenter->icon = icon;
547 contentCenter->title = reviewTitle;
548 contentCenter->description = reviewSubTitle;
549 contentCenter->subText = "Swipe to review";
550 contentCenter->smallTitle = NULL;
551 contentCenter->iconHug = 0;
552 contentCenter->padding = false;
553 contentCenter->illustrType = ICON_ILLUSTRATION;
554}
555
556static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
557{
558 if (finishTitle != NULL) {
559 return finishTitle;
560 }
561 switch (operationType & REAL_TYPE_MASK) {
562 case TYPE_TRANSACTION:
563 return "Sign transaction";
564 case TYPE_MESSAGE:
565 return "Sign message";
566 default:
567 return "Sign operation";
568 }
569}
570
571static void prepareReviewLastPage(nbgl_operationType_t operationType,
572 nbgl_contentInfoLongPress_t *infoLongPress,
573 const nbgl_icon_details_t *icon,
574 const char *finishTitle)
575{
576 infoLongPress->text = getFinishTitle(operationType, finishTitle);
577 infoLongPress->icon = icon;
578 infoLongPress->longPressText = "Hold to sign";
579 infoLongPress->longPressToken = CONFIRM_TOKEN;
580}
581
582static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
583 nbgl_contentInfoButton_t *infoButton,
584 const nbgl_icon_details_t *icon,
585 const char *finishTitle)
586{
587 infoButton->text = getFinishTitle(operationType, finishTitle);
588 infoButton->icon = icon;
589 infoButton->buttonText = "Approve";
590 infoButton->buttonToken = CONFIRM_TOKEN;
591}
592
593static const char *getRejectReviewText(nbgl_operationType_t operationType)
594{
595 UNUSED(operationType);
596 return "Reject";
597}
598
599// function called when navigating (or exiting) modal details pages
600// or when skip choice is displayed
601static void pageModalCallback(int token, uint8_t index)
602{
603 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
604
605 if (token == INFOS_TIP_BOX_TOKEN) {
606 // the icon next to info type alias has been touched
607 displayFullValuePage(activeTipBox.infos.infoTypes[index],
608 activeTipBox.infos.infoContents[index],
609 &activeTipBox.infos.infoExtensions[index]);
610 return;
611 }
612 else if (token == INFO_ALIAS_TOKEN) {
613 // the icon next to info type alias has been touched, but coming from review
614 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
615 genericContext.currentInfos->infoContents[index],
616 &genericContext.currentInfos->infoExtensions[index]);
617 return;
618 }
619 nbgl_pageRelease(modalPageContext);
620 modalPageContext = NULL;
621 if (token == NAV_TOKEN) {
622 if (index == EXIT_PAGE) {
623 // redraw the background layer
625 nbgl_refresh();
626 }
627 else {
628 displayDetailsPage(index, false);
629 }
630 }
631 if (token == MODAL_NAV_TOKEN) {
632 if (index == EXIT_PAGE) {
633 // redraw the background layer
635 nbgl_refresh();
636 }
637 else {
638 displayTagValueListModalPage(index, false);
639 }
640 }
641 else if (token == QUIT_TOKEN) {
642 // redraw the background layer
644 nbgl_refresh();
645 }
646 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
647 // if in "warning context", go back to security report
648 if (reviewWithWarnCtx.securityReportLevel == 2) {
649 reviewWithWarnCtx.securityReportLevel = 1;
650 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
651 }
652 else {
653 // redraw the background layer
655 nbgl_refresh();
656 }
657 }
658 else if (token == SKIP_TOKEN) {
659 if (index == 0) {
660 // display the last forward only review page, whatever it is
661 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
662 }
663 else {
664 // display background, which should be the page where skip has been touched
666 nbgl_refresh();
667 }
668 }
669 else if (token == CHOICE_TOKEN) {
670 if (index == 0) {
671 if (onModalConfirm != NULL) {
672 onModalConfirm();
673 onModalConfirm = NULL;
674 }
675 }
676 else {
677 // display background, which should be the page where skip has been touched
679 nbgl_refresh();
680 }
681 }
682}
683
684// generic callback for all pages except modal
685static void pageCallback(int token, uint8_t index)
686{
687 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
688 if (token == QUIT_TOKEN) {
689 if (onQuit != NULL) {
690 onQuit();
691 }
692 }
693 else if (token == CONTINUE_TOKEN) {
694 if (onContinue != NULL) {
695 onContinue();
696 }
697 }
698 else if (token == CHOICE_TOKEN) {
699 if (onChoice != NULL) {
700 onChoice((index == 0) ? true : false);
701 }
702 }
703 else if (token == ACTION_BUTTON_TOKEN) {
704 if ((index == 0) && (onAction != NULL)) {
705 onAction();
706 }
707 else if ((index == 1) && (onQuit != NULL)) {
708 onQuit();
709 }
710 }
711#ifdef NBGL_QRCODE
712 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
713 displayAddressQRCode();
714 }
715#endif // NBGL_QRCODE
716 else if (token == CONFIRM_TOKEN) {
717 if (onChoice != NULL) {
718 onChoice(true);
719 }
720 }
721 else if (token == REJECT_TOKEN) {
722 if (onChoice != NULL) {
723 onChoice(false);
724 }
725 }
726 else if (token == DETAILS_BUTTON_TOKEN) {
727 displayDetails(genericContext.detailsItem,
728 genericContext.detailsvalue,
729 genericContext.detailsWrapping);
730 }
731 else if (token == NAV_TOKEN) {
732 if (index == EXIT_PAGE) {
733 if (onQuit != NULL) {
734 onQuit();
735 }
736 }
737 else {
738 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
739 displayGenericContextPage(index, false);
740 }
741 else if (navType == REVIEW_NAV) {
742 displayReviewPage(index, false);
743 }
744 else {
745 displaySettingsPage(index, false);
746 }
747 }
748 }
749 else if (token == NEXT_TOKEN) {
750 if (onNav != NULL) {
751 displayReviewPage(navInfo.activePage + 1, false);
752 }
753 else {
754 displayGenericContextPage(navInfo.activePage + 1, false);
755 }
756 }
757 else if (token == BACK_TOKEN) {
758 if (onNav != NULL) {
759 displayReviewPage(navInfo.activePage - 1, true);
760 }
761 else {
762 displayGenericContextPage(navInfo.activePage - 1, false);
763 }
764 }
765 else if (token == SKIP_TOKEN) {
766 // display a modal warning to confirm skip
767 displaySkipWarning();
768 }
769 else if (token == VALUE_ALIAS_TOKEN) {
770 // the icon next to value alias has been touched
771 const nbgl_contentTagValue_t *pair;
772 if (genericContext.currentPairs != NULL) {
773 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
774 }
775 else {
776 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
777 }
778 displayFullValuePage(pair->item, pair->value, pair->extension);
779 }
780 else if (token == BLIND_WARNING_TOKEN) {
781 reviewWithWarnCtx.isIntro = false;
782 reviewWithWarnCtx.warning = NULL;
783 displaySecurityReport(1 << BLIND_SIGNING_WARN);
784 }
785 else if (token == WARNING_BUTTON_TOKEN) {
786 // top-right button, display the security modal
787 reviewWithWarnCtx.securityReportLevel = 1;
788 // if preset is used
789 if (reviewWithWarnCtx.warning->predefinedSet) {
790 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
791 }
792 else {
793 // use customized warning
794 if (reviewWithWarnCtx.isIntro) {
795 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
796 }
797 else {
798 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
799 }
800 }
801 }
802 else if (token == TIP_BOX_TOKEN) {
803 // if warning context is valid and if W3C directly display same as top-right
804 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
805 reviewWithWarnCtx.securityReportLevel = 1;
806 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
807 }
808 else {
809 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
810 }
811 }
812 else { // probably a control provided by caller
813 if (onContentAction != NULL) {
814 onContentAction(token, index, navInfo.activePage);
815 }
816 if (onControls != NULL) {
817 onControls(token, index);
818 }
819 }
820}
821
822// callback used for confirmation
823static void tickerCallback(void)
824{
825 nbgl_pageRelease(pageContext);
826 if (onQuit != NULL) {
827 onQuit();
828 }
829}
830
831// function used to display the current page in review
832static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
833{
834 nbgl_pageContent_t content = {0};
835
836 if ((onNav == NULL) || (onNav(page, &content) == false)) {
837 return;
838 }
839
840 // override some fields
841 content.title = pageTitle;
842 content.isTouchableTitle = true;
843 content.titleToken = QUIT_TOKEN;
844 content.tuneId = TUNE_TAP_CASUAL;
845
846 navInfo.activePage = page;
847 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
848
849 if (forceFullRefresh) {
851 }
852 else {
854 }
855}
856
857// function used to display the current page in review
858static void displayReviewPage(uint8_t page, bool forceFullRefresh)
859{
860 nbgl_pageContent_t content = {0};
861
862 // ensure the page is valid
863 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
864 return;
865 }
866 navInfo.activePage = page;
867 if ((onNav == NULL) || (onNav(navInfo.activePage, &content) == false)) {
868 return;
869 }
870
871 // override some fields
872 content.title = NULL;
873 content.isTouchableTitle = false;
874 content.tuneId = TUNE_TAP_CASUAL;
875
876 if (content.type == INFO_LONG_PRESS) { // last page
877 // for forward only review without known length...
878 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
879 navInfo.nbPages = navInfo.activePage + 1;
880 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
881 if (forwardNavOnly) {
882 // remove the "Skip" button
883 navInfo.skipText = NULL;
884 }
885 }
886
887 // override smallCaseForValue for tag/value types to false
888 if (content.type == TAG_VALUE_DETAILS) {
890 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
891 // button)
893 }
894 else if (content.type == TAG_VALUE_LIST) {
895 content.tagValueList.smallCaseForValue = false;
896 }
897 else if (content.type == TAG_VALUE_CONFIRM) {
899 // use confirm token for black button
900 content.tagValueConfirm.confirmationToken = CONFIRM_TOKEN;
901 }
902
903 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
904
905 if (forceFullRefresh) {
907 }
908 else {
910 }
911}
912
913// Helper that does the computing of which nbgl_content_t and which element in the content should be
914// displayed for the next generic context navigation page
915static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t pageIdx,
916 nbgl_content_t *content,
917 uint8_t *p_nbElementsInNextPage,
918 bool *p_flag)
919{
920 int8_t nextContentIdx = genericContext.currentContentIdx;
921 int16_t nextElementIdx = genericContext.currentElementIdx;
922 uint8_t nbElementsInNextPage;
923
924 // Retrieve info on the next page
925 genericContextGetPageInfo(pageIdx, &nbElementsInNextPage, p_flag);
926 *p_nbElementsInNextPage = nbElementsInNextPage;
927
928 // Handle forward navigation:
929 // add to current index the number of pairs of the currently active page
930 if (pageIdx > navInfo.activePage) {
931 uint8_t nbElementsInCurrentPage;
932
933 genericContextGetPageInfo(navInfo.activePage, &nbElementsInCurrentPage, NULL);
934 nextElementIdx += nbElementsInCurrentPage;
935
936 // Handle case where the content to be displayed is in the next content
937 // In such case start at element index 0.
938 // If currentContentElementNb == 0, means not initialized, so skip
939 if ((nextElementIdx >= genericContext.currentContentElementNb)
940 && (genericContext.currentContentElementNb > 0)) {
941 nextContentIdx += 1;
942 nextElementIdx = 0;
943 }
944 }
945
946 // Handle backward navigation:
947 // add to current index the number of pairs of the currently active page
948 if (pageIdx < navInfo.activePage) {
949 // Backward navigation: remove to current index the number of pairs of the current page
950 nextElementIdx -= nbElementsInNextPage;
951
952 // Handle case where the content to be displayed is in the previous content
953 // In such case set a negative number as element index so that it is handled
954 // later once the previous content is accessible so that its elements number
955 // can be retrieved.
956 if (nextElementIdx < 0) {
957 nextContentIdx -= 1;
958 nextElementIdx = -nbElementsInNextPage;
959 }
960 }
961
962 const nbgl_content_t *p_content;
963 // Retrieve next content
964 if ((nextContentIdx == -1) && (genericContext.hasStartingContent)) {
965 p_content = &STARTING_CONTENT;
966 }
967 else if ((nextContentIdx == genericContext.genericContents.nbContents)
968 && (genericContext.hasFinishingContent)) {
969 p_content = &FINISHING_CONTENT;
970 }
971 else {
972 p_content = getContentAtIdx(&genericContext.genericContents, nextContentIdx, content);
973
974 if (p_content == NULL) {
975 LOG_DEBUG(USE_CASE_LOGGER, "Fail to retrieve content\n");
976 return NULL;
977 }
978 }
979
980 // Handle cases where we are going to display data from a new content:
981 // - navigation to a previous or to the next content
982 // - First page display (genericContext.currentContentElementNb == 0)
983 //
984 // In such case we need to:
985 // - Update genericContext.currentContentIdx
986 // - Update genericContext.currentContentElementNb
987 // - Update onContentAction callback
988 // - Update nextElementIdx in case it was previously not calculable
989 if ((nextContentIdx != genericContext.currentContentIdx)
990 || (genericContext.currentContentElementNb == 0)) {
991 genericContext.currentContentIdx = nextContentIdx;
992 genericContext.currentContentElementNb = getContentNbElement(p_content);
993 onContentAction = PIC(p_content->contentActionCallback);
994 if (nextElementIdx < 0) {
995 nextElementIdx = genericContext.currentContentElementNb + nextElementIdx;
996 }
997 }
998
999 // Sanity check
1000 if ((nextElementIdx < 0) || (nextElementIdx >= genericContext.currentContentElementNb)) {
1002 "Invalid element index %d / %d\n",
1003 nextElementIdx,
1004 genericContext.currentContentElementNb);
1005 return NULL;
1006 }
1007
1008 // Update genericContext elements
1009 genericContext.currentElementIdx = nextElementIdx;
1010 navInfo.activePage = pageIdx;
1011
1012 return p_content;
1013}
1014
1015// Helper that generates a nbgl_pageContent_t to be displayed for the next generic context
1016// navigation page
1017static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
1018 uint8_t nbElementsInPage,
1019 bool flag,
1020 nbgl_pageContent_t *pageContent)
1021{
1022 uint8_t nextElementIdx = genericContext.currentElementIdx;
1023
1024 pageContent->title = pageTitle;
1025 pageContent->isTouchableTitle = false;
1026 pageContent->titleToken = QUIT_TOKEN;
1027 pageContent->tuneId = TUNE_TAP_CASUAL;
1028
1029 pageContent->type = p_content->type;
1030 switch (pageContent->type) {
1031 case CENTERED_INFO:
1032 memcpy(&pageContent->centeredInfo,
1033 &p_content->content.centeredInfo,
1034 sizeof(pageContent->centeredInfo));
1035 break;
1036 case EXTENDED_CENTER:
1037 memcpy(&pageContent->extendedCenter,
1038 &p_content->content.extendedCenter,
1039 sizeof(pageContent->extendedCenter));
1040 break;
1041 case INFO_LONG_PRESS:
1042 memcpy(&pageContent->infoLongPress,
1043 &p_content->content.infoLongPress,
1044 sizeof(pageContent->infoLongPress));
1045 break;
1046
1047 case INFO_BUTTON:
1048 memcpy(&pageContent->infoButton,
1049 &p_content->content.infoButton,
1050 sizeof(pageContent->infoButton));
1051 break;
1052
1053 case TAG_VALUE_LIST: {
1054 nbgl_contentTagValueList_t *p_tagValueList = &pageContent->tagValueList;
1055
1056 // memorize pairs (or callback) for usage when alias is used
1057 genericContext.currentPairs = p_content->content.tagValueList.pairs;
1058 genericContext.currentCallback = p_content->content.tagValueList.callback;
1059
1060 if (flag) {
1061 // Flag can be set if the pair is too long to fit or because it needs
1062 // to be displayed as centered info.
1063
1064 // First retrieve the pair
1065 const nbgl_layoutTagValue_t *pair;
1066
1067 if (p_content->content.tagValueList.pairs != NULL) {
1068 pair = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1069 }
1070 else {
1071 pair = PIC(p_content->content.tagValueList.callback(nextElementIdx));
1072 }
1073
1074 if (pair->centeredInfo) {
1075 pageContent->type = EXTENDED_CENTER;
1076 prepareReviewFirstPage(&pageContent->extendedCenter.contentCenter,
1077 pair->valueIcon,
1078 pair->item,
1079 pair->value);
1080
1081 // Skip population of nbgl_contentTagValueList_t structure
1082 p_tagValueList = NULL;
1083 }
1084 else {
1085 // if the pair is too long to fit, we use a TAG_VALUE_DETAILS content
1086 pageContent->type = TAG_VALUE_DETAILS;
1087 pageContent->tagValueDetails.detailsButtonText = "More";
1088 pageContent->tagValueDetails.detailsButtonIcon = NULL;
1089 pageContent->tagValueDetails.detailsButtonToken = DETAILS_BUTTON_TOKEN;
1090
1091 p_tagValueList = &pageContent->tagValueDetails.tagValueList;
1092
1093 // Backup pair info for easy data access upon click on button
1094 genericContext.detailsItem = pair->item;
1095 genericContext.detailsvalue = pair->value;
1096 genericContext.detailsWrapping = p_content->content.tagValueList.wrapping;
1097 }
1098 }
1099
1100 if (p_tagValueList != NULL) {
1101 p_tagValueList->nbPairs = nbElementsInPage;
1102 p_tagValueList->token = p_content->content.tagValueList.token;
1103 if (p_content->content.tagValueList.pairs != NULL) {
1104 p_tagValueList->pairs
1105 = PIC(&p_content->content.tagValueList.pairs[nextElementIdx]);
1106 // parse pairs to check if any contains an alias for value
1107 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1108 if (p_tagValueList->pairs[i].aliasValue) {
1109 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1110 break;
1111 }
1112 }
1113 }
1114 else {
1115 p_tagValueList->pairs = NULL;
1116 p_tagValueList->callback = p_content->content.tagValueList.callback;
1117 p_tagValueList->startIndex = nextElementIdx;
1118 // parse pairs to check if any contains an alias for value
1119 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1120 const nbgl_layoutTagValue_t *pair
1121 = PIC(p_content->content.tagValueList.callback(nextElementIdx + i));
1122 if (pair->aliasValue) {
1123 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1124 break;
1125 }
1126 }
1127 }
1128 p_tagValueList->smallCaseForValue = false;
1130 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1131 }
1132
1133 break;
1134 }
1135 case TAG_VALUE_CONFIRM: {
1136 nbgl_contentTagValueList_t *p_tagValueList;
1137 // only display a TAG_VALUE_CONFIRM if we are at the last page
1138 if ((nextElementIdx + nbElementsInPage)
1140 memcpy(&pageContent->tagValueConfirm,
1141 &p_content->content.tagValueConfirm,
1142 sizeof(pageContent->tagValueConfirm));
1143 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1144 }
1145 else {
1146 // else display it as a TAG_VALUE_LIST
1147 pageContent->type = TAG_VALUE_LIST;
1148 p_tagValueList = &pageContent->tagValueList;
1149 }
1150 p_tagValueList->nbPairs = nbElementsInPage;
1151 p_tagValueList->pairs
1152 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1153 // parse pairs to check if any contains an alias for value
1154 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1155 if (p_tagValueList->pairs[i].aliasValue) {
1156 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1157 break;
1158 }
1159 }
1160 // memorize pairs (or callback) for usage when alias is used
1161 genericContext.currentPairs = p_tagValueList->pairs;
1162 genericContext.currentCallback = p_tagValueList->callback;
1163 break;
1164 }
1165 case SWITCHES_LIST:
1166 pageContent->switchesList.nbSwitches = nbElementsInPage;
1167 pageContent->switchesList.switches
1168 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1169 break;
1170 case INFOS_LIST:
1171 pageContent->infosList.nbInfos = nbElementsInPage;
1172 pageContent->infosList.infoTypes
1173 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1174 pageContent->infosList.infoContents
1175 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1176 break;
1177 case CHOICES_LIST:
1178 memcpy(&pageContent->choicesList,
1179 &p_content->content.choicesList,
1180 sizeof(pageContent->choicesList));
1181 pageContent->choicesList.nbChoices = nbElementsInPage;
1182 pageContent->choicesList.names
1183 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1184 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1185 && (p_content->content.choicesList.initChoice
1186 < nextElementIdx + nbElementsInPage)) {
1187 pageContent->choicesList.initChoice
1188 = p_content->content.choicesList.initChoice - nextElementIdx;
1189 }
1190 else {
1191 pageContent->choicesList.initChoice = nbElementsInPage;
1192 }
1193 break;
1194 case BARS_LIST:
1195 pageContent->barsList.nbBars = nbElementsInPage;
1196 pageContent->barsList.barTexts
1197 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1198 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1199 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1200 break;
1201 default:
1202 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1203 return false;
1204 }
1205
1206 bool isFirstOrLastPage
1207 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1208 || (p_content->type == INFO_LONG_PRESS);
1209 nbgl_operationType_t operationType
1210 = (navType == STREAMING_NAV)
1211 ? bundleNavContext.reviewStreaming.operationType
1212 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1213
1214 // if first or last page of review and blind/risky operation, add the warning top-right button
1215 if (isFirstOrLastPage
1216 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1217 // if issue is only Web3Checks "no threat", use privacy icon
1218 if ((operationType & NO_THREAT_OPERATION)
1219 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1220 pageContent->topRightIcon = &PRIVACY_ICON;
1221 }
1222 else {
1223 pageContent->topRightIcon = &WARNING_ICON;
1224 }
1225
1226 pageContent->topRightToken
1227 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1228 }
1229
1230 return true;
1231}
1232
1233// function used to display the current page in generic context navigation mode
1234static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1235{
1236 // Retrieve next page parameters
1237 nbgl_content_t content;
1238 uint8_t nbElementsInPage;
1239 bool flag;
1240 const nbgl_content_t *p_content = NULL;
1241
1242 if (navType == STREAMING_NAV) {
1243 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1244 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1245 bundleNavContext.reviewStreaming.skipCallback();
1246 }
1247 return;
1248 }
1249 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1250 bundleNavReviewStreamingChoice(true);
1251 return;
1252 }
1253 }
1254
1255 if (navInfo.activePage == pageIdx) {
1256 p_content
1257 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1258 }
1259 else if (navInfo.activePage < pageIdx) {
1260 // Support going more than one step forward.
1261 // It occurs when initializing a navigation on an arbitrary page
1262 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1263 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1264 }
1265 }
1266 else {
1267 if (pageIdx - navInfo.activePage > 1) {
1268 // We don't support going more than one step backward as it doesn't occurs for now?
1269 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1270 return;
1271 }
1272 p_content
1273 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1274 }
1275
1276 if (p_content == NULL) {
1277 return;
1278 }
1279
1280 // Create next page content
1281 nbgl_pageContent_t pageContent = {0};
1282 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1283 return;
1284 }
1285
1286 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1287
1288 if (forceFullRefresh) {
1290 }
1291 else {
1293 }
1294}
1295
1296// from the current details context, return a pointer on the details at the given page
1297static const char *getDetailsPageAt(uint8_t detailsPage)
1298{
1299 uint8_t page = 0;
1300 const char *currentChar = detailsContext.value;
1301 while (page < detailsPage) {
1302 uint16_t nbLines
1303 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1304 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1305 uint16_t len;
1306 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1307 currentChar,
1310 &len,
1311 detailsContext.wrapping);
1312 len -= 3;
1313 currentChar = currentChar + len;
1314 }
1315 page++;
1316 }
1317 return currentChar;
1318}
1319
1320// function used to display the current page in details review mode
1321static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1322{
1323 static nbgl_layoutTagValue_t currentPair;
1324 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1325 .nbPages = detailsContext.nbPages,
1326 .navType = NAV_WITH_BUTTONS,
1327 .quitToken = QUIT_TOKEN,
1328 .navWithButtons.navToken = NAV_TOKEN,
1329 .navWithButtons.quitButton = true,
1330 .navWithButtons.backButton = true,
1331 .navWithButtons.quitText = NULL,
1332 .progressIndicator = false,
1333 .tuneId = TUNE_TAP_CASUAL};
1335 .topRightIcon = NULL,
1336 .tagValueList.nbPairs = 1,
1337 .tagValueList.pairs = &currentPair,
1338 .tagValueList.smallCaseForValue = true,
1339 .tagValueList.wrapping = detailsContext.wrapping};
1340
1341 if (modalPageContext != NULL) {
1342 nbgl_pageRelease(modalPageContext);
1343 }
1344 currentPair.item = detailsContext.tag;
1345 // if move backward or first page
1346 if (detailsPage <= detailsContext.currentPage) {
1347 // recompute current start from beginning
1348 currentPair.value = getDetailsPageAt(detailsPage);
1349 forceFullRefresh = true;
1350 }
1351 // else move forward
1352 else {
1353 currentPair.value = detailsContext.nextPageStart;
1354 }
1355 detailsContext.currentPage = detailsPage;
1356 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1357 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1358
1359 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1360 uint16_t len;
1361 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1362 currentPair.value,
1365 &len,
1366 detailsContext.wrapping);
1367 len -= 3;
1368 // memorize next position to save processing
1369 detailsContext.nextPageStart = currentPair.value + len;
1370 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1371 // chars by "..."
1373 }
1374 else {
1375 detailsContext.nextPageStart = NULL;
1376 content.tagValueList.nbMaxLinesForValue = 0;
1377 }
1378 if (info.nbPages == 1) {
1379 // if only one page, no navigation bar, and use a footer instead
1380 info.navWithButtons.quitText = "Close";
1381 }
1382 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1383
1384 if (forceFullRefresh) {
1386 }
1387 else {
1389 }
1390}
1391
1392// function used to display the content of a full value, when touching an alias of a tag/value pair
1393static void displayFullValuePage(const char *backText,
1394 const char *aliasText,
1395 const nbgl_contentValueExt_t *extension)
1396{
1397 const char *modalTitle
1398 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1399 if (extension->aliasType == INFO_LIST_ALIAS) {
1400 genericContext.currentInfos = extension->infolist;
1401 displayInfosListModal(modalTitle, extension->infolist, true);
1402 }
1403 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1404 genericContext.currentTagValues = extension->tagValuelist;
1405 displayTagValueListModal(extension->tagValuelist);
1406 }
1407 else {
1408 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1409 .withLeftBorder = true,
1410 .onActionCallback = &modalLayoutTouchCallback,
1411 .tapActionText = NULL};
1413 .separationLine = false,
1414 .backAndText.token = 0,
1415 .backAndText.tuneId = TUNE_TAP_CASUAL,
1416 .backAndText.text = modalTitle};
1417 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1418 // add header with the tag part of the pair, to go back
1419 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1420 // add either QR Code or full value text
1421 if (extension->aliasType == QR_CODE_ALIAS) {
1422#ifdef NBGL_QRCODE
1423 nbgl_layoutQRCode_t qrCode
1424 = {.url = extension->fullValue,
1425 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1426 .text2 = extension->explanation,
1427 .centered = true,
1428 .offsetY = 0};
1429
1430 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1431#endif // NBGL_QRCODE
1432 }
1433 else {
1434 const char *info;
1435 // add full value text
1436 if (extension->aliasType == ENS_ALIAS) {
1437 info = "ENS names are resolved by Ledger backend.";
1438 }
1439 else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
1440 info = "This account label comes from your Address Book in Ledger Live.";
1441 }
1442 else {
1443 info = extension->explanation;
1444 }
1446 genericContext.modalLayout, aliasText, extension->fullValue, info);
1447 }
1448 // draw & refresh
1449 nbgl_layoutDraw(genericContext.modalLayout);
1450 nbgl_refresh();
1451 }
1452}
1453
1454// function used to display the modal containing tip-box infos
1455static void displayInfosListModal(const char *modalTitle,
1456 const nbgl_contentInfoList_t *infos,
1457 bool fromReview)
1458{
1460 .nbPages = 1,
1461 .navType = NAV_WITH_BUTTONS,
1462 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1463 .navWithButtons.navToken = NAV_TOKEN,
1464 .navWithButtons.quitButton = false,
1465 .navWithButtons.backButton = true,
1466 .navWithButtons.quitText = NULL,
1467 .progressIndicator = false,
1468 .tuneId = TUNE_TAP_CASUAL};
1469 nbgl_pageContent_t content
1470 = {.type = INFOS_LIST,
1471 .topRightIcon = NULL,
1472 .infosList.nbInfos = infos->nbInfos,
1473 .infosList.withExtensions = infos->withExtensions,
1474 .infosList.infoTypes = infos->infoTypes,
1475 .infosList.infoContents = infos->infoContents,
1476 .infosList.infoExtensions = infos->infoExtensions,
1477 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1478 .title = modalTitle,
1479 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1480 .tuneId = TUNE_TAP_CASUAL};
1481
1482 if (modalPageContext != NULL) {
1483 nbgl_pageRelease(modalPageContext);
1484 }
1485 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1486
1488}
1489
1490// function used to display the modal containing alias tag-value pairs
1491static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1492{
1493 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1494 .nbPages = detailsContext.nbPages,
1495 .navType = NAV_WITH_BUTTONS,
1496 .quitToken = QUIT_TOKEN,
1497 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1498 .navWithButtons.quitButton = true,
1499 .navWithButtons.backButton = true,
1500 .navWithButtons.quitText = NULL,
1501 .progressIndicator = false,
1502 .tuneId = TUNE_TAP_CASUAL};
1504 .topRightIcon = NULL,
1505 .tagValueList.smallCaseForValue = true,
1506 .tagValueList.wrapping = detailsContext.wrapping};
1507 uint8_t nbElementsInPage;
1508
1509 // if first page or forward
1510 if (detailsContext.currentPage <= pageIdx) {
1511 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1512 }
1513 else {
1514 // backward direction
1515 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1516 detailsContext.currentPairIdx -= nbElementsInPage;
1517 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1518 detailsContext.currentPairIdx -= nbElementsInPage;
1519 }
1520 detailsContext.currentPage = pageIdx;
1521
1522 content.tagValueList.pairs
1523 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1524 content.tagValueList.nbPairs = nbElementsInPage;
1525 detailsContext.currentPairIdx += nbElementsInPage;
1526 if (info.nbPages == 1) {
1527 // if only one page, no navigation bar, and use a footer instead
1528 info.navWithButtons.quitText = "Close";
1529 }
1530
1531 if (modalPageContext != NULL) {
1532 nbgl_pageRelease(modalPageContext);
1533 }
1534 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1535
1536 if (forceFullRefresh) {
1538 }
1539 else {
1541 }
1542}
1543
1544#ifdef NBGL_QRCODE
1545static void displayAddressQRCode(void)
1546{
1547 // display the address as QR Code
1548 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1549 .withLeftBorder = true,
1550 .onActionCallback = &modalLayoutTouchCallback,
1551 .tapActionText = NULL};
1552 nbgl_layoutHeader_t headerDesc = {
1553 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1554 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1555 .text1 = NULL,
1556 .centered = true,
1557 .offsetY = 0};
1558
1559 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1560 // add empty header for better look
1561 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1562 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1563 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1564 addressConfirmationContext.tagValuePairs[0].value,
1566 false);
1567
1568 if (nbLines <= QRCODE_NB_MAX_LINES) {
1569 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1570 }
1571 else {
1572 // only keep beginning and end of text, and add ... in the middle
1573 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1574 addressConfirmationContext.tagValuePairs[0].value,
1576 QRCODE_NB_MAX_LINES,
1577 reducedAddress,
1578 QRCODE_REDUCED_ADDR_LEN);
1579 qrCode.text2 = reducedAddress; // in gray
1580 }
1581
1582 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1583
1585 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1586 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1587 nbgl_refresh();
1588}
1589
1590#endif // NBGL_QRCODE
1591
1592// called when header is touched on modal page, to dismiss it
1593static void modalLayoutTouchCallback(int token, uint8_t index)
1594{
1595 UNUSED(index);
1596 if (token == DISMISS_QR_TOKEN) {
1597 // dismiss modal
1598 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1599 addressConfirmationContext.modalLayout = NULL;
1601 }
1602 else if (token == DISMISS_WARNING_TOKEN) {
1603 // dismiss modal
1604 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1605 // if already at first level, simply redraw the background
1606 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1607 reviewWithWarnCtx.modalLayout = NULL;
1609 }
1610 else {
1611 // We are at level 2 of warning modal, so re-display the level 1
1612 reviewWithWarnCtx.securityReportLevel = 1;
1613 // if preset is used
1614 if (reviewWithWarnCtx.warning->predefinedSet) {
1615 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1616 }
1617 else {
1618 // use customized warning
1619 const nbgl_warningDetails_t *details
1620 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1621 : reviewWithWarnCtx.warning->reviewDetails;
1622 displayCustomizedSecurityReport(details);
1623 }
1624 return;
1625 }
1626 }
1627 else if (token == DISMISS_DETAILS_TOKEN) {
1628 // dismiss modal
1629 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1630 // if already at first level, simply redraw the background
1631 if (choiceWithDetailsCtx.level <= 1) {
1632 choiceWithDetailsCtx.modalLayout = NULL;
1634 }
1635 else {
1636 // We are at level 2 of details modal, so re-display the level 1
1637 choiceWithDetailsCtx.level = 1;
1638 choiceWithDetailsCtx.modalLayout
1639 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1640 }
1641 }
1642 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1643 if (sharedContext.usage == SHARE_CTX_REVIEW_WITH_WARNING) {
1644 // dismiss modal before creating a new one
1645 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1646 reviewWithWarnCtx.securityReportLevel = 2;
1647 // if preset is used
1648 if (reviewWithWarnCtx.warning->predefinedSet) {
1649 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1650 }
1651 else {
1652 // use customized warning
1653 const nbgl_warningDetails_t *details
1654 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1655 : reviewWithWarnCtx.warning->reviewDetails;
1656 if (details->type == BAR_LIST_WARNING) {
1657 displayCustomizedSecurityReport(
1658 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1659 }
1660 }
1661 }
1662 else if (sharedContext.usage == SHARE_CTX_CHOICE_WITH_DETAILS) {
1663 const nbgl_warningDetails_t *details
1664 = &choiceWithDetailsCtx.details->barList.details[token - FIRST_WARN_BAR_TOKEN];
1665 if (details->title != NO_TYPE_WARNING) {
1666 // dismiss modal before creating a new one
1667 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1668 choiceWithDetailsCtx.level = 2;
1669 choiceWithDetailsCtx.modalLayout
1670 = displayModalDetails(details, DISMISS_DETAILS_TOKEN);
1671 }
1672 }
1673 return;
1674 }
1675 else {
1676 // dismiss modal
1677 nbgl_layoutRelease(genericContext.modalLayout);
1678 genericContext.modalLayout = NULL;
1680 }
1681 nbgl_refresh();
1682}
1683
1684// called when item are touched in layout
1685static void layoutTouchCallback(int token, uint8_t index)
1686{
1687 // choice buttons in initial warning page
1688 if (token == WARNING_CHOICE_TOKEN) {
1689 if (index == 0) { // top button to exit
1690 reviewWithWarnCtx.choiceCallback(false);
1691 }
1692 else { // bottom button to continue to review
1693 reviewWithWarnCtx.isIntro = false;
1694 if (reviewWithWarnCtx.isStreaming) {
1695 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1696 reviewWithWarnCtx.icon,
1697 reviewWithWarnCtx.reviewTitle,
1698 reviewWithWarnCtx.reviewSubTitle,
1699 reviewWithWarnCtx.choiceCallback,
1700 false);
1701 }
1702 else {
1703 useCaseReview(reviewWithWarnCtx.operationType,
1704 reviewWithWarnCtx.tagValueList,
1705 reviewWithWarnCtx.icon,
1706 reviewWithWarnCtx.reviewTitle,
1707 reviewWithWarnCtx.reviewSubTitle,
1708 reviewWithWarnCtx.finishTitle,
1709 &activeTipBox,
1710 reviewWithWarnCtx.choiceCallback,
1711 false,
1712 false);
1713 }
1714 }
1715 }
1716 // top-right button in initial warning page
1717 else if (token == WARNING_BUTTON_TOKEN) {
1718 // start directly at first level of security report
1719 reviewWithWarnCtx.securityReportLevel = 1;
1720 // if preset is used
1721 if (reviewWithWarnCtx.warning->predefinedSet) {
1722 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1723 }
1724 else {
1725 // use customized warning
1726 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1727 ? reviewWithWarnCtx.warning->introDetails
1728 : reviewWithWarnCtx.warning->reviewDetails;
1729 displayCustomizedSecurityReport(details);
1730 }
1731 }
1732 // top-right button in choice with details page
1733 else if (token == CHOICE_DETAILS_TOKEN) {
1734 choiceWithDetailsCtx.level = 1;
1735 choiceWithDetailsCtx.modalLayout
1736 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1737 }
1738}
1739
1740// called when skip button is touched in footer, during forward only review
1741static void displaySkipWarning(void)
1742{
1744 .cancelText = "Go back to review",
1745 .centeredInfo.text1 = "Skip review?",
1746 .centeredInfo.text2
1747 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1748 .centeredInfo.text3 = NULL,
1749 .centeredInfo.style = LARGE_CASE_INFO,
1750 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1751 .centeredInfo.offsetY = 0,
1752 .confirmationText = "Yes, skip",
1753 .confirmationToken = SKIP_TOKEN,
1754 .tuneId = TUNE_TAP_CASUAL,
1755 .modal = true};
1756 if (modalPageContext != NULL) {
1757 nbgl_pageRelease(modalPageContext);
1758 }
1759 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1761}
1762
1763#ifdef NBGL_KEYPAD
1764// called to update the keypad and automatically show / hide:
1765// - backspace key if no digits are present
1766// - validation key if the min digit is reached
1767// - keypad if the max number of digit is reached
1768static void updateKeyPad(bool add)
1769{
1770 bool enableValidate, enableBackspace, enableDigits;
1771 bool redrawKeypad = false;
1773
1774 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1775 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1776 enableBackspace = (keypadContext.pinLen > 0);
1777 if (add) {
1778 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1779 || // activate "validate" button on keypad
1780 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1781 || // deactivate "digits" on keypad
1782 (keypadContext.pinLen == 1)) { // activate "backspace"
1783 redrawKeypad = true;
1784 }
1785 }
1786 else { // remove
1787 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1788 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1789 || // deactivate "validate" button on keypad
1790 (keypadContext.pinLen
1791 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1792 redrawKeypad = true;
1793 }
1794 }
1795 if (keypadContext.hidden == true) {
1796 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1797 }
1798 else {
1800 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1801 }
1802 if (redrawKeypad) {
1804 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1805 }
1806
1807 if ((!add) && (keypadContext.pinLen == 0)) {
1808 // Full refresh to fully clean the bullets when reaching 0 digits
1809 mode = FULL_COLOR_REFRESH;
1810 }
1812}
1813
1814// called when a key is touched on the keypad
1815static void keypadCallback(char touchedKey)
1816{
1817 switch (touchedKey) {
1818 case BACKSPACE_KEY:
1819 if (keypadContext.pinLen > 0) {
1820 keypadContext.pinLen--;
1821 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1822 }
1823 updateKeyPad(false);
1824 break;
1825
1826 case VALIDATE_KEY:
1827 // Gray out keyboard / buttons as a first user feedback
1828 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1831
1832 onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1833 break;
1834
1835 default:
1836 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1837 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1838 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1839 keypadContext.pinLen++;
1840 }
1841 updateKeyPad(true);
1842 }
1843 break;
1844 }
1845}
1846
1847// called to create a keypad, with either hidden or visible digits
1848static void keypadGenericUseCase(const char *title,
1849 uint8_t minDigits,
1850 uint8_t maxDigits,
1851 uint8_t backToken,
1852 bool shuffled,
1853 bool hidden,
1854 tune_index_e tuneId,
1855 nbgl_pinValidCallback_t validatePinCallback,
1856 nbgl_layoutTouchCallback_t actionCallback)
1857{
1858 nbgl_layoutDescription_t layoutDescription = {0};
1860 .separationLine = true,
1861 .backAndText.token = backToken,
1862 .backAndText.tuneId = tuneId,
1863 .backAndText.text = NULL};
1864 int status = -1;
1865
1866 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
1867 return;
1868 }
1869
1870 reset_callbacks_and_context();
1871 // reset the keypad context
1872 memset(&keypadContext, 0, sizeof(KeypadContext_t));
1873
1874 // get a layout
1875 layoutDescription.onActionCallback = actionCallback;
1876 layoutDescription.modal = false;
1877 layoutDescription.withLeftBorder = false;
1878 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
1879 keypadContext.hidden = hidden;
1880
1881 // set back key in header
1882 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
1883
1884 // add keypad
1885 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
1886 if (status < 0) {
1887 return;
1888 }
1889 // add keypad content
1891 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
1892
1893 if (status < 0) {
1894 return;
1895 }
1896
1897 // validation pin callback
1898 onValidatePin = validatePinCallback;
1899 // pin code acceptable lengths
1900 keypadContext.pinMinDigits = minDigits;
1901 keypadContext.pinMaxDigits = maxDigits;
1902
1903 nbgl_layoutDraw(keypadContext.layoutCtx);
1905}
1906#endif
1907
1922static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
1923 const nbgl_contentTagValueList_t *tagValueList,
1924 uint8_t startIndex,
1925 bool isSkippable,
1926 bool hasConfirmationButton,
1927 bool hasDetailsButton,
1928 bool *requireSpecificDisplay)
1929{
1930 uint8_t nbPairsInPage = 0;
1931 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
1932 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
1933
1934 // if the review is skippable, it means that there is less height for tag/value pairs
1935 // the small centering header becomes a touchable header
1936 if (isSkippable) {
1937 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
1938 }
1939
1940 *requireSpecificDisplay = false;
1941 while (nbPairsInPage < nbPairs) {
1942 const nbgl_layoutTagValue_t *pair;
1943 nbgl_font_id_e value_font;
1944 uint16_t nbLines;
1945
1946 // margin between pairs
1947 // 12 or 24 px between each tag/value pair
1948 if (nbPairsInPage > 0) {
1949 currentHeight += INTER_TAG_VALUE_MARGIN;
1950 }
1951 // fetch tag/value pair strings.
1952 if (tagValueList->pairs != NULL) {
1953 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
1954 }
1955 else {
1956 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
1957 }
1958
1959 if (pair->forcePageStart && nbPairsInPage > 0) {
1960 // This pair must be at the top of a page
1961 break;
1962 }
1963
1964 if (pair->centeredInfo) {
1965 if (nbPairsInPage > 0) {
1966 // This pair must be at the top of a page
1967 break;
1968 }
1969 else {
1970 // This pair is the only one of the page and has a specific display behavior
1971 nbPairsInPage = 1;
1972 *requireSpecificDisplay = true;
1973 break;
1974 }
1975 }
1976
1977 // tag height
1978 currentHeight += nbgl_getTextHeightInWidth(
1979 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
1980 // space between tag and value
1981 currentHeight += 4;
1982 // set value font
1983 if (tagValueList->smallCaseForValue) {
1984 value_font = SMALL_REGULAR_FONT;
1985 }
1986 else {
1987 value_font = LARGE_MEDIUM_FONT;
1988 }
1989 // value height
1990 currentHeight += nbgl_getTextHeightInWidth(
1991 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1992 // nb lines for value
1994 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1995 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
1996 if (nbPairsInPage == 0) {
1997 // Pair too long to fit in a single screen
1998 // It will be the only one of the page and has a specific display behavior
1999 nbPairsInPage = 1;
2000 *requireSpecificDisplay = true;
2001 }
2002 break;
2003 }
2004 nbPairsInPage++;
2005 }
2006 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
2007 // let's check if it still fits with a CONFIRMATION button, and if not,
2008 // remove the last pair
2009 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
2010 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
2011 if (currentHeight > maxUsableHeight) {
2012 nbPairsInPage--;
2013 }
2014 }
2015 // do the same with just a details button
2016 else if (hasDetailsButton) {
2017 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
2018 if (currentHeight > maxUsableHeight) {
2019 nbPairsInPage--;
2020 }
2021 }
2022 return nbPairsInPage;
2023}
2024
2034static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
2035 const nbgl_contentTagValueList_t *tagValueList,
2036 uint8_t startIndex)
2037{
2038 uint8_t nbPairsInPage = 0;
2039 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2040 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2041
2042 while (nbPairsInPage < nbPairs) {
2043 const nbgl_layoutTagValue_t *pair;
2044
2045 // margin between pairs
2046 // 12 or 24 px between each tag/value pair
2047 if (nbPairsInPage > 0) {
2048 currentHeight += INTER_TAG_VALUE_MARGIN;
2049 }
2050 // fetch tag/value pair strings.
2051 if (tagValueList->pairs != NULL) {
2052 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2053 }
2054 else {
2055 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2056 }
2057
2058 // tag height
2059 currentHeight += nbgl_getTextHeightInWidth(
2060 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2061 // space between tag and value
2062 currentHeight += 4;
2063
2064 // value height
2065 currentHeight += nbgl_getTextHeightInWidth(
2066 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2067
2068 // we have reached the maximum height, it means than there are to many pairs
2069 if (currentHeight >= maxUsableHeight) {
2070 break;
2071 }
2072 nbPairsInPage++;
2073 }
2074 return nbPairsInPage;
2075}
2076
2077static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2078 uint8_t pageIdxStart,
2079 bool isLast,
2080 bool isSkippable)
2081{
2082 uint8_t nbElements = 0;
2083 uint8_t nbPages = 0;
2084 uint8_t nbElementsInPage;
2085 uint8_t elemIdx = 0;
2086 bool flag;
2087
2088 nbElements = getContentNbElement(content);
2089
2090 while (nbElements > 0) {
2091 flag = 0;
2092 // if the current page is not the first one (or last), a navigation bar exists
2093 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2094 if (content->type == TAG_VALUE_LIST) {
2095 nbElementsInPage = getNbTagValuesInPage(nbElements,
2096 &content->content.tagValueList,
2097 elemIdx,
2098 isSkippable,
2099 false,
2100 false,
2101 &flag);
2102 }
2103 else if (content->type == TAG_VALUE_CONFIRM) {
2104 nbElementsInPage = getNbTagValuesInPage(nbElements,
2106 elemIdx,
2107 isSkippable,
2108 isLast,
2109 !isLast,
2110 &flag);
2111 }
2112 else if (content->type == INFOS_LIST) {
2113 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
2114 nbElements, &content->content.infosList, elemIdx, hasNav);
2115 }
2116 else if (content->type == SWITCHES_LIST) {
2117 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
2118 nbElements, &content->content.switchesList, elemIdx, hasNav);
2119 }
2120 else if (content->type == BARS_LIST) {
2121 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
2122 nbElements, &content->content.barsList, elemIdx, hasNav);
2123 }
2124 else if (content->type == CHOICES_LIST) {
2125 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2126 nbElements, &content->content.choicesList, elemIdx, hasNav);
2127 }
2128 else {
2129 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2130 }
2131
2132 elemIdx += nbElementsInPage;
2133 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2134 nbElements -= nbElementsInPage;
2135 nbPages++;
2136 }
2137
2138 return nbPages;
2139}
2140
2141static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2142 uint8_t pageIdxStart,
2143 bool isSkippable)
2144{
2145 uint8_t nbPages = 0;
2146 nbgl_content_t content;
2147 const nbgl_content_t *p_content;
2148
2149 for (int i = 0; i < genericContents->nbContents; i++) {
2150 p_content = getContentAtIdx(genericContents, i, &content);
2151 if (p_content == NULL) {
2152 return 0;
2153 }
2154 nbPages += getNbPagesForContent(p_content,
2155 pageIdxStart + nbPages,
2156 (i == (genericContents->nbContents - 1)),
2157 isSkippable);
2158 }
2159
2160 return nbPages;
2161}
2162
2163static void prepareAddressConfirmationPages(const char *address,
2164 const nbgl_contentTagValueList_t *tagValueList,
2165 nbgl_content_t *firstPageContent,
2166 nbgl_content_t *secondPageContent)
2167{
2168 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2169
2170 addressConfirmationContext.tagValuePairs[0].item = "Address";
2171 addressConfirmationContext.tagValuePairs[0].value = address;
2172 addressConfirmationContext.nbPairs = 1;
2173
2174 // First page
2175 firstPageContent->type = TAG_VALUE_CONFIRM;
2176 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2177
2178#ifdef NBGL_QRCODE
2179 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2180 // only use "Show as QR" when address & pairs are not fitting in a single page
2181 if ((tagValueList != NULL) && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2183 bool flag;
2184 // copy in intermediate structure
2185 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2186 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2187 &tagValueList->pairs[i],
2188 sizeof(nbgl_contentTagValue_t));
2189 addressConfirmationContext.nbPairs++;
2190 }
2191 // check how many can fit in a page
2192 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2193 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2194 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2195 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2196 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2197 // if they don't all fit, keep only the address
2198 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2199 addressConfirmationContext.nbPairs = 1;
2200 }
2201 }
2202 else {
2203 tagValueConfirm->detailsButtonText = NULL;
2204 }
2205 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2206#else // NBGL_QRCODE
2207 tagValueConfirm->detailsButtonText = NULL;
2208 tagValueConfirm->detailsButtonIcon = NULL;
2209#endif // NBGL_QRCODE
2210 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2211 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2212 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2213 tagValueConfirm->tagValueList.smallCaseForValue = false;
2214 tagValueConfirm->tagValueList.nbMaxLinesForValue = 0;
2215 tagValueConfirm->tagValueList.wrapping = false;
2216 // if it's an extended address verif, it takes 2 pages, so display a "Tap to continue", and
2217 // no confirmation button
2218 if ((tagValueList != NULL)
2219 && (tagValueList->nbPairs > (addressConfirmationContext.nbPairs - 1))) {
2220 tagValueConfirm->detailsButtonText = "Show as QR";
2221 tagValueConfirm->confirmationText = NULL;
2222 // the second page is dedicated to the extended tag/value pairs
2223 secondPageContent->type = TAG_VALUE_CONFIRM;
2224 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2225 tagValueConfirm->confirmationText = "Confirm";
2226 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2227 tagValueConfirm->detailsButtonText = NULL;
2228 tagValueConfirm->detailsButtonIcon = NULL;
2229 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2230 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2231 tagValueConfirm->tagValueList.nbPairs
2232 = tagValueList->nbPairs - (addressConfirmationContext.nbPairs - 1);
2233 tagValueConfirm->tagValueList.pairs
2234 = &tagValueList->pairs[addressConfirmationContext.nbPairs - 1];
2235 }
2236 else {
2237 // otherwise no tap to continue but a confirmation button
2238 tagValueConfirm->confirmationText = "Confirm";
2239 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2240 }
2241}
2242
2243static void bundleNavStartHome(void)
2244{
2245 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2246
2247 useCaseHomeExt(context->appName,
2248 context->appIcon,
2249 context->tagline,
2250 context->settingContents != NULL ? true : false,
2251 &context->homeAction,
2252 bundleNavStartSettings,
2253 context->quitCallback);
2254}
2255
2256static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2257{
2258 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2259
2260 nbgl_useCaseGenericSettings(context->appName,
2261 initSettingPage,
2262 context->settingContents,
2263 context->infosList,
2264 bundleNavStartHome);
2265}
2266
2267static void bundleNavStartSettings(void)
2268{
2269 bundleNavStartSettingsAtPage(0);
2270}
2271
2272static void bundleNavReviewConfirmRejection(void)
2273{
2274 bundleNavContext.review.choiceCallback(false);
2275}
2276
2277static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2278 nbgl_callback_t callback)
2279{
2280 const char *title;
2281 const char *confirmText;
2282 // clear skip and blind bits
2283 operationType
2284 &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION);
2285 if (operationType == TYPE_TRANSACTION) {
2286 title = "Reject transaction?";
2287 confirmText = "Go back to transaction";
2288 }
2289 else if (operationType == TYPE_MESSAGE) {
2290 title = "Reject message?";
2291 confirmText = "Go back to message";
2292 }
2293 else {
2294 title = "Reject operation?";
2295 confirmText = "Go back to operation";
2296 }
2297
2298 // display a choice to confirm/cancel rejection
2299 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2300}
2301
2302static void bundleNavReviewChoice(bool confirm)
2303{
2304 if (confirm) {
2305 bundleNavContext.review.choiceCallback(true);
2306 }
2307 else {
2308 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2309 bundleNavReviewConfirmRejection);
2310 }
2311}
2312
2313static void bundleNavReviewStreamingConfirmRejection(void)
2314{
2315 bundleNavContext.reviewStreaming.choiceCallback(false);
2316}
2317
2318static void bundleNavReviewStreamingChoice(bool confirm)
2319{
2320 if (confirm) {
2321 // Display a spinner if it wasn't the finish step
2322 if (STARTING_CONTENT.type != INFO_LONG_PRESS) {
2323 nbgl_useCaseSpinner("Processing");
2324 }
2325 bundleNavContext.reviewStreaming.choiceCallback(true);
2326 }
2327 else {
2328 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2329 bundleNavReviewStreamingConfirmRejection);
2330 }
2331}
2332
2333// function used to display the details in modal (from Choice with details or from Customized
2334// security report) it can be either a single page with text or QR code, or a list of touchable bars
2335// leading to single pages
2336static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token)
2337{
2338 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2339 .withLeftBorder = true,
2340 .onActionCallback = modalLayoutTouchCallback,
2341 .tapActionText = NULL};
2343 .separationLine = true,
2344 .backAndText.icon = NULL,
2345 .backAndText.tuneId = TUNE_TAP_CASUAL,
2346 .backAndText.token = token};
2347 uint8_t i;
2348 nbgl_layout_t *layout;
2349
2350 layout = nbgl_layoutGet(&layoutDescription);
2351 headerDesc.backAndText.text = details->title;
2352 nbgl_layoutAddHeader(layout, &headerDesc);
2353 if (details->type == BAR_LIST_WARNING) {
2354 // if more than one warning, so use a list of touchable bars
2355 for (i = 0; i < details->barList.nbBars; i++) {
2356 nbgl_layoutBar_t bar;
2357 bar.text = details->barList.texts[i];
2358 bar.subText = details->barList.subTexts[i];
2359 bar.iconRight
2360 = (details->barList.details[i].type != NO_TYPE_WARNING) ? &PUSH_ICON : NULL;
2361 bar.iconLeft = details->barList.icons[i];
2362 bar.token = FIRST_WARN_BAR_TOKEN + i;
2363 bar.tuneId = TUNE_TAP_CASUAL;
2364 bar.large = false;
2365 bar.inactive = false;
2366 nbgl_layoutAddTouchableBar(layout, &bar);
2368 }
2369 }
2370 else if (details->type == QRCODE_WARNING) {
2371#ifdef NBGL_QRCODE
2372 // display a QR Code
2373 nbgl_layoutAddQRCode(layout, &details->qrCode);
2374#endif // NBGL_QRCODE
2375 headerDesc.backAndText.text = details->title;
2376 }
2377 else if (details->type == CENTERED_INFO_WARNING) {
2378 // display a centered info
2379 nbgl_layoutAddContentCenter(layout, &details->centeredInfo);
2380 headerDesc.separationLine = false;
2381 }
2382 nbgl_layoutDraw(layout);
2383 nbgl_refresh();
2384 return layout;
2385}
2386
2387// function used to display the security level page in modal
2388// it can be either a single page with text or QR code, or a list of touchable bars leading
2389// to single pages
2390static void displaySecurityReport(uint32_t set)
2391{
2392 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2393 .withLeftBorder = true,
2394 .onActionCallback = modalLayoutTouchCallback,
2395 .tapActionText = NULL};
2397 .separationLine = true,
2398 .backAndText.icon = NULL,
2399 .backAndText.tuneId = TUNE_TAP_CASUAL,
2400 .backAndText.token = DISMISS_WARNING_TOKEN};
2401 nbgl_layoutFooter_t footerDesc
2402 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2403 uint8_t i;
2404 uint8_t nbWarnings = 0;
2405 const char *provider;
2406
2407 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2408
2409 // count the number of warnings
2410 for (i = 0; i < NB_WARNING_TYPES; i++) {
2411 if ((set & (1 << i)) && (i != W3C_NO_THREAT_WARN)) {
2412 nbWarnings++;
2413 }
2414 }
2415
2416 // display a list of touchable bars in some conditions:
2417 // - we must be in the first level of security report
2418 // - and be in the review itself (not from the intro/warning screen)
2419 //
2420 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2421 for (i = 0; i < NB_WARNING_TYPES; i++) {
2422 if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) {
2423 nbgl_layoutBar_t bar = {0};
2424 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2425 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2426 bar.subText = securityReportItems[i].subText;
2427 }
2428 else {
2429 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2430 }
2431 bar.text = securityReportItems[i].text;
2432 if (i != W3C_NO_THREAT_WARN) {
2433 bar.iconRight = &PUSH_ICON;
2434 bar.token = FIRST_WARN_BAR_TOKEN + i;
2435 bar.tuneId = TUNE_TAP_CASUAL;
2436 }
2437 else {
2439 }
2440 bar.iconLeft = securityReportItems[i].icon;
2441 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2442 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2443 }
2444 }
2445 headerDesc.backAndText.text = "Security report";
2446 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2447 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2448 nbgl_refresh();
2449 return;
2450 }
2451 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2452 provider = reviewWithWarnCtx.warning->reportProvider;
2453 }
2454 else {
2455 provider = "[unknown]";
2456 }
2457 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2458 size_t urlLen = 0;
2459 char *destStr
2460 = tmpString
2461 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2462#ifdef NBGL_QRCODE
2463 // display a QR Code
2464 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2465 .text1 = reviewWithWarnCtx.warning->reportUrl,
2466 .text2 = "Scan to view full report",
2467 .centered = true,
2468 .offsetY = 0};
2469 // add "https://"" as prefix of the given URL
2470 snprintf(destStr,
2471 W3C_DESCRIPTION_MAX_LEN / 2,
2472 "https://%s",
2473 reviewWithWarnCtx.warning->reportUrl);
2474 urlLen = strlen(destStr) + 1;
2475 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2476 footerDesc.emptySpace.height = 24;
2477#endif // NBGL_QRCODE
2478 // use the next part of destStr for back bar text
2479 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2480 headerDesc.backAndText.text = destStr + urlLen;
2481 }
2482 else if (set & (1 << BLIND_SIGNING_WARN)) {
2483 // display a centered if in review
2484 nbgl_contentCenter_t info = {0};
2485 info.icon = &LARGE_WARNING_ICON;
2486 info.title = "This transaction cannot be Clear Signed";
2487 info.description
2488 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2489 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2490 "ledger.com/e8";
2491 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2492 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2493 headerDesc.separationLine = false;
2494 }
2495 else if (set & (1 << W3C_ISSUE_WARN)) {
2496 // if W3 Checks issue, display a centered info
2497 nbgl_contentCenter_t info = {0};
2498 info.icon = &LARGE_WARNING_ICON;
2499 info.title = "Transaction Check unavailable";
2500 info.description
2501 = "If you're not using the\nLedger Live app, Transaction Check might not work. If your "
2502 "are using Ledger Live, reject the transaction and try again.\n\nGet help at "
2503 "ledger.com/e11";
2504 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2505 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2506 headerDesc.separationLine = false;
2507 }
2508 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2509 if (footerDesc.emptySpace.height > 0) {
2510 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2511 }
2512 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2513 nbgl_refresh();
2514}
2515
2516// function used to display the security level page in modal
2517// it can be either a single page with text or QR code, or a list of touchable bars leading
2518// to single pages
2519static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2520{
2521 reviewWithWarnCtx.modalLayout = displayModalDetails(details, DISMISS_WARNING_TOKEN);
2522}
2523
2524// function used to display the initial warning page when starting a "review with warning"
2525static void displayInitialWarning(void)
2526{
2527 // Play notification sound
2528#ifdef HAVE_PIEZO_SOUND
2529 tune_index_e tune = TUNE_RESERVED;
2530#endif // HAVE_PIEZO_SOUND
2531 nbgl_layoutDescription_t layoutDescription;
2532 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2533 .token = WARNING_CHOICE_TOKEN,
2534 .topText = "Back to safety",
2535 .style = ROUNDED_AND_FOOTER_STYLE,
2536 .tuneId = TUNE_TAP_CASUAL};
2537 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2538 .separationLine = false,
2539 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2540 uint32_t set = reviewWithWarnCtx.warning->predefinedSet
2541 & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2542
2543 reviewWithWarnCtx.isIntro = true;
2544
2545 layoutDescription.modal = false;
2546 layoutDescription.withLeftBorder = true;
2547
2548 layoutDescription.onActionCallback = layoutTouchCallback;
2549 layoutDescription.tapActionText = NULL;
2550
2551 layoutDescription.ticker.tickerCallback = NULL;
2552 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2553
2554 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2555 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2557 reviewWithWarnCtx.layoutCtx,
2558 (set == (1 << BLIND_SIGNING_WARN)) ? &INFO_I_ICON : &QRCODE_ICON,
2559 WARNING_BUTTON_TOKEN,
2560 TUNE_TAP_CASUAL);
2561 }
2562 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2563 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2564 reviewWithWarnCtx.warning->introTopRightIcon,
2565 WARNING_BUTTON_TOKEN,
2566 TUNE_TAP_CASUAL);
2567 }
2568 // add main content
2569 // if predefined content is configured, use it preferably
2570 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2571 nbgl_contentCenter_t info = {0};
2572
2573 const char *provider;
2574
2575 // default icon
2576 info.icon = &LARGE_WARNING_ICON;
2577
2578 // use small title only if not Blind signing only
2579 if (set != (1 << BLIND_SIGNING_WARN)) {
2580 if (reviewWithWarnCtx.warning->reportProvider) {
2581 provider = reviewWithWarnCtx.warning->reportProvider;
2582 }
2583 else {
2584 provider = "[unknown]";
2585 }
2586 info.smallTitle = tmpString;
2587 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2588 }
2589
2590 if (set == (1 << BLIND_SIGNING_WARN)) {
2591 info.title = "Blind signing ahead";
2592 info.description
2593 = "This transaction's details are not fully verifiable. If you sign, you could "
2594 "lose all your assets.";
2595 buttonsInfo.bottomText = "Continue anyway";
2596#ifdef HAVE_PIEZO_SOUND
2597 tune = TUNE_NEUTRAL;
2598#endif // HAVE_PIEZO_SOUND
2599 }
2600 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2601 info.title = "Potential risk";
2602 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2603 info.description = "Unidentified risk";
2604 }
2605 else {
2606 info.description = reviewWithWarnCtx.warning->providerMessage;
2607 }
2608 buttonsInfo.bottomText = "Accept risk and continue";
2609#ifdef HAVE_PIEZO_SOUND
2610 tune = TUNE_NEUTRAL;
2611#endif // HAVE_PIEZO_SOUND
2612 }
2613 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2614 info.title = "Critical threat";
2615 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2616 info.description = "Unidentified threat";
2617 }
2618 else {
2619 info.description = reviewWithWarnCtx.warning->providerMessage;
2620 }
2621 buttonsInfo.bottomText = "Accept threat and continue";
2622#ifdef HAVE_PIEZO_SOUND
2623 tune = TUNE_ERROR;
2624#endif // HAVE_PIEZO_SOUND
2625 }
2626 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2627 }
2628 else if (reviewWithWarnCtx.warning->info != NULL) {
2629 // if no predefined content, use custom one
2630 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2631#ifdef HAVE_PIEZO_SOUND
2632 tune = TUNE_LOOK_AT_ME;
2633#endif // HAVE_PIEZO_SOUND
2634 }
2635 // add button and footer on bottom
2636 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2637
2638#ifdef HAVE_PIEZO_SOUND
2639 if (tune != TUNE_RESERVED) {
2640 os_io_seph_cmd_piezo_play_tune(tune);
2641 }
2642#endif // HAVE_PIEZO_SOUND
2643 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2644 nbgl_refresh();
2645}
2646
2647// function to factorize code for reviews tipbox
2648static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2649{
2650 const char *predefinedTipBoxText = NULL;
2651
2652 // if warning is valid and a warning requires a tip-box
2653 if (reviewWithWarnCtx.warning) {
2654 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2655 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2656 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2657 }
2658 else {
2659 predefinedTipBoxText = "Transaction Check unavailable";
2660 }
2661 }
2662 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2663 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2664 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2665 }
2666 else {
2667 predefinedTipBoxText = "Critical threat detected.";
2668 }
2669 }
2670 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2671 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2672 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2673 }
2674 else {
2675 predefinedTipBoxText = "Potential risk detected.";
2676 }
2677 }
2678 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2679 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2680 predefinedTipBoxText
2681 = "No threat detected by Transaction Check but blind signing required.";
2682 }
2683 else {
2684 predefinedTipBoxText = "No threat detected by Transaction Check.";
2685 }
2686 }
2687 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2688 predefinedTipBoxText = "Blind signing required.";
2689 }
2690 }
2691
2692 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2693 // do not display "Swipe to review" if a tip-box is displayed
2694 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2695 if (predefinedTipBoxText != NULL) {
2696 genericContext.validWarningCtx = true;
2697 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2698 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2699 }
2700 else {
2701 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2702 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2703 }
2704 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2705 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2706 }
2707}
2708
2709// function to factorize code for all simple reviews
2710static void useCaseReview(nbgl_operationType_t operationType,
2711 const nbgl_contentTagValueList_t *tagValueList,
2712 const nbgl_icon_details_t *icon,
2713 const char *reviewTitle,
2714 const char *reviewSubTitle,
2715 const char *finishTitle,
2716 const nbgl_tipBox_t *tipBox,
2717 nbgl_choiceCallback_t choiceCallback,
2718 bool isLight,
2719 bool playNotifSound)
2720{
2721 reset_callbacks_and_context();
2722
2723 bundleNavContext.review.operationType = operationType;
2724 bundleNavContext.review.choiceCallback = choiceCallback;
2725
2726 // memorize context
2727 onChoice = bundleNavReviewChoice;
2728 navType = GENERIC_NAV;
2729 pageTitle = NULL;
2730
2731 genericContext.genericContents.contentsList = localContentsList;
2732 genericContext.genericContents.nbContents = 3;
2733 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2734
2735 // First a centered info
2736 STARTING_CONTENT.type = EXTENDED_CENTER;
2737 prepareReviewFirstPage(
2738 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2739
2740 // Prepare un tipbox if needed
2741 initWarningTipBox(tipBox);
2742
2743 // Then the tag/value pairs
2744 localContentsList[1].type = TAG_VALUE_LIST;
2745 memcpy(&localContentsList[1].content.tagValueList,
2746 tagValueList,
2748 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2749
2750 // The last page
2751 if (isLight) {
2752 localContentsList[2].type = INFO_BUTTON;
2753 prepareReviewLightLastPage(
2754 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2755 }
2756 else {
2757 localContentsList[2].type = INFO_LONG_PRESS;
2758 prepareReviewLastPage(
2759 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2760 }
2761
2762 // compute number of pages & fill navigation structure
2763 uint8_t nbPages = getNbPagesForGenericContents(
2764 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2765 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2766
2767 // Play notification sound if required
2768 if (playNotifSound) {
2769#ifdef HAVE_PIEZO_SOUND
2770 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2771#endif // HAVE_PIEZO_SOUND
2772 }
2773
2774 displayGenericContextPage(0, true);
2775}
2776
2777// function to factorize code for all streaming reviews
2778static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
2779 const nbgl_icon_details_t *icon,
2780 const char *reviewTitle,
2781 const char *reviewSubTitle,
2782 nbgl_choiceCallback_t choiceCallback,
2783 bool playNotifSound)
2784{
2785 reset_callbacks_and_context();
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 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
2850 .centeredInfo.text1 = appName,
2851 .centeredInfo.text3 = NULL,
2852 .centeredInfo.style = LARGE_CASE_INFO,
2853 .centeredInfo.offsetY = 0,
2854 .footerText = NULL,
2855 .bottomButtonStyle = QUIT_APP_TEXT,
2856 .tapActionText = NULL,
2857 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
2858 .topRightToken = CONTINUE_TOKEN,
2859 .tuneId = TUNE_TAP_CASUAL};
2860 reset_callbacks_and_context();
2861
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_and_context();
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_and_context();
3333
3334 // memorize context
3335 onQuit = quitCallback;
3336 pageTitle = appName;
3337 navType = GENERIC_NAV;
3338
3339 if (settingContents != NULL) {
3340 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3341 }
3342 if (infosList != NULL) {
3343 genericContext.hasFinishingContent = true;
3344 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3345 FINISHING_CONTENT.type = INFOS_LIST;
3346 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3347 }
3348
3349 // fill navigation structure
3350 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3351 if (infosList != NULL) {
3352 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3353 }
3354
3355 prepareNavInfo(false, nbPages, NULL);
3356
3357 displayGenericContextPage(initPage, true);
3358}
3359
3371void nbgl_useCaseGenericConfiguration(const char *title,
3372 uint8_t initPage,
3373 const nbgl_genericContents_t *contents,
3374 nbgl_callback_t quitCallback)
3375{
3376 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3377}
3378
3396 const char *appName,
3397 const nbgl_icon_details_t *appIcon,
3398 const char *tagline,
3399 const uint8_t
3400 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3401 const nbgl_genericContents_t *settingContents,
3402 const nbgl_contentInfoList_t *infosList,
3403 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3404 nbgl_callback_t quitCallback)
3405{
3406 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3407
3408 reset_callbacks_and_context();
3409
3410 context->appName = appName;
3411 context->appIcon = appIcon;
3412 context->tagline = tagline;
3413 context->settingContents = settingContents;
3414 context->infosList = infosList;
3415 if (action != NULL) {
3416 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3417 }
3418 else {
3419 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3420 }
3421 context->quitCallback = quitCallback;
3422
3423 if (initSettingPage != INIT_HOME_PAGE) {
3424 bundleNavStartSettingsAtPage(initSettingPage);
3425 }
3426 else {
3427 bundleNavStartHome();
3428 }
3429}
3430
3438void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3439{
3440 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3441 .tickerIntervale = 0, // not periodic
3442 .tickerValue = STATUS_SCREEN_DURATION};
3443 nbgl_pageInfoDescription_t info = {0};
3444
3445 reset_callbacks_and_context();
3446
3447 onQuit = quitCallback;
3448 if (isSuccess) {
3449#ifdef HAVE_PIEZO_SOUND
3450 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3451#endif // HAVE_PIEZO_SOUND
3452 }
3453 info.centeredInfo.icon = isSuccess ? &CHECK_CIRCLE_ICON : &DENIED_CIRCLE_ICON;
3455 info.centeredInfo.text1 = message;
3456 info.tapActionText = "";
3457 info.tapActionToken = QUIT_TOKEN;
3458 info.tuneId = TUNE_TAP_CASUAL;
3459 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3461}
3462
3470 nbgl_callback_t quitCallback)
3471{
3472 const char *msg;
3473 bool isSuccess;
3474 switch (reviewStatusType) {
3476 msg = "Operation signed";
3477 isSuccess = true;
3478 break;
3480 msg = "Operation rejected";
3481 isSuccess = false;
3482 break;
3484 msg = "Transaction signed";
3485 isSuccess = true;
3486 break;
3488 msg = "Transaction rejected";
3489 isSuccess = false;
3490 break;
3492 msg = "Message signed";
3493 isSuccess = true;
3494 break;
3496 msg = "Message rejected";
3497 isSuccess = false;
3498 break;
3500 msg = "Address verified";
3501 isSuccess = true;
3502 break;
3504 msg = "Address verification\ncancelled";
3505 isSuccess = false;
3506 break;
3507 default:
3508 return;
3509 }
3510 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3511}
3512
3526 const char *message,
3527 const char *subMessage,
3528 const char *confirmText,
3529 const char *cancelText,
3530 nbgl_choiceCallback_t callback)
3531{
3533 // check params
3534 if ((confirmText == NULL) || (cancelText == NULL)) {
3535 return;
3536 }
3537 reset_callbacks_and_context();
3538
3539 info.cancelText = cancelText;
3540 info.centeredInfo.text1 = message;
3541 info.centeredInfo.text2 = subMessage;
3543 info.centeredInfo.icon = icon;
3544 info.confirmationText = confirmText;
3545 info.confirmationToken = CHOICE_TOKEN;
3546 info.tuneId = TUNE_TAP_CASUAL;
3547
3548 onChoice = callback;
3549 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3551}
3552
3568 const char *message,
3569 const char *subMessage,
3570 const char *confirmText,
3571 const char *cancelText,
3572 nbgl_genericDetails_t *details,
3573 nbgl_choiceCallback_t callback)
3574{
3575 nbgl_layoutDescription_t layoutDescription;
3576 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = cancelText,
3577 .token = CHOICE_TOKEN,
3578 .topText = confirmText,
3579 .style = ROUNDED_AND_FOOTER_STYLE,
3580 .tuneId = TUNE_TAP_CASUAL};
3581 nbgl_contentCenter_t centeredInfo = {0};
3582 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
3583 .separationLine = false,
3584 .emptySpace.height = MEDIUM_CENTERING_HEADER};
3585
3586 // check params
3587 if ((confirmText == NULL) || (cancelText == NULL)) {
3588 return;
3589 }
3590
3591 reset_callbacks_and_context();
3592
3593 onChoice = callback;
3594 layoutDescription.modal = false;
3595 layoutDescription.withLeftBorder = true;
3596
3597 layoutDescription.onActionCallback = layoutTouchCallback;
3598 layoutDescription.tapActionText = NULL;
3599
3600 layoutDescription.ticker.tickerCallback = NULL;
3601 sharedContext.usage = SHARE_CTX_CHOICE_WITH_DETAILS;
3602 choiceWithDetailsCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
3603 choiceWithDetailsCtx.details = details;
3604
3605 nbgl_layoutAddHeader(choiceWithDetailsCtx.layoutCtx, &headerDesc);
3606 nbgl_layoutAddChoiceButtons(choiceWithDetailsCtx.layoutCtx, &buttonsInfo);
3607 centeredInfo.icon = icon;
3608 centeredInfo.title = message;
3609 centeredInfo.description = subMessage;
3610 nbgl_layoutAddContentCenter(choiceWithDetailsCtx.layoutCtx, &centeredInfo);
3611
3612 if (details != NULL) {
3614 choiceWithDetailsCtx.layoutCtx, &SEARCH_ICON, CHOICE_DETAILS_TOKEN, TUNE_TAP_CASUAL);
3615 }
3616
3617 nbgl_layoutDraw(choiceWithDetailsCtx.layoutCtx);
3619}
3620
3634void nbgl_useCaseConfirm(const char *message,
3635 const char *subMessage,
3636 const char *confirmText,
3637 const char *cancelText,
3638 nbgl_callback_t callback)
3639{
3640 // Don't reset callback or nav context as this is just a modal.
3641
3642 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3643 .centeredInfo.text1 = message,
3644 .centeredInfo.text2 = subMessage,
3645 .centeredInfo.text3 = NULL,
3646 .centeredInfo.style = LARGE_CASE_INFO,
3647 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3648 .centeredInfo.offsetY = 0,
3649 .confirmationText = confirmText,
3650 .confirmationToken = CHOICE_TOKEN,
3651 .tuneId = TUNE_TAP_CASUAL,
3652 .modal = true};
3653 onModalConfirm = callback;
3654 if (modalPageContext != NULL) {
3655 nbgl_pageRelease(modalPageContext);
3656 }
3657 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3659}
3660
3672 const char *message,
3673 const char *actionText,
3674 nbgl_callback_t callback)
3675{
3676 nbgl_pageContent_t content = {0};
3677
3678 reset_callbacks_and_context();
3679 // memorize callback
3680 onAction = callback;
3681
3682 content.tuneId = TUNE_TAP_CASUAL;
3683 content.type = INFO_BUTTON;
3684 content.infoButton.buttonText = actionText;
3685 content.infoButton.text = message;
3686 content.infoButton.icon = icon;
3687 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3688
3689 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3691}
3692
3705 const char *reviewTitle,
3706 const char *reviewSubTitle,
3707 const char *rejectText,
3708 nbgl_callback_t continueCallback,
3709 nbgl_callback_t rejectCallback)
3710{
3711 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3712 .footerToken = QUIT_TOKEN,
3713 .tapActionText = NULL,
3714 .isSwipeable = true,
3715 .tapActionToken = CONTINUE_TOKEN,
3716 .topRightStyle = NO_BUTTON_STYLE,
3717 .actionButtonText = NULL,
3718 .tuneId = TUNE_TAP_CASUAL};
3719
3720 reset_callbacks_and_context();
3721
3722 info.centeredInfo.icon = icon;
3723 info.centeredInfo.text1 = reviewTitle;
3724 info.centeredInfo.text2 = reviewSubTitle;
3725 info.centeredInfo.text3 = "Swipe to review";
3727 info.centeredInfo.offsetY = 0;
3728 onQuit = rejectCallback;
3729 onContinue = continueCallback;
3730
3731#ifdef HAVE_PIEZO_SOUND
3732 // Play notification sound
3733 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3734#endif // HAVE_PIEZO_SOUND
3735
3736 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3737 nbgl_refresh();
3738}
3739
3744void nbgl_useCaseRegularReview(uint8_t initPage,
3745 uint8_t nbPages,
3746 const char *rejectText,
3747 nbgl_layoutTouchCallback_t buttonCallback,
3748 nbgl_navCallback_t navCallback,
3749 nbgl_choiceCallback_t choiceCallback)
3750{
3751 reset_callbacks_and_context();
3752
3753 // memorize context
3754 onChoice = choiceCallback;
3755 onNav = navCallback;
3756 onControls = buttonCallback;
3757 forwardNavOnly = false;
3758 navType = REVIEW_NAV;
3759
3760 // fill navigation structure
3761 UNUSED(rejectText);
3762 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3763
3764 displayReviewPage(initPage, true);
3765}
3766
3780 const nbgl_pageInfoLongPress_t *infoLongPress,
3781 const char *rejectText,
3782 nbgl_choiceCallback_t callback)
3783{
3784 uint8_t offset = 0;
3785
3786 reset_callbacks_and_context();
3787
3788 // memorize context
3789 onChoice = callback;
3790 navType = GENERIC_NAV;
3791 pageTitle = NULL;
3792 bundleNavContext.review.operationType = TYPE_OPERATION;
3793
3794 genericContext.genericContents.contentsList = localContentsList;
3795 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3796
3797 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3798 localContentsList[offset].type = TAG_VALUE_LIST;
3799 memcpy(&localContentsList[offset].content.tagValueList,
3800 tagValueList,
3802 offset++;
3803 }
3804
3805 localContentsList[offset].type = INFO_LONG_PRESS;
3806 memcpy(&localContentsList[offset].content.infoLongPress,
3807 infoLongPress,
3808 sizeof(nbgl_pageInfoLongPress_t));
3809 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3810 offset++;
3811
3812 genericContext.genericContents.nbContents = offset;
3813
3814 // compute number of pages & fill navigation structure
3815 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3816 UNUSED(rejectText);
3817 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3818
3819 displayGenericContextPage(0, true);
3820}
3821
3836 const nbgl_pageInfoLongPress_t *infoLongPress,
3837 const char *rejectText,
3838 nbgl_choiceCallback_t callback)
3839{
3840 uint8_t offset = 0;
3841
3842 reset_callbacks_and_context();
3843
3844 // memorize context
3845 onChoice = callback;
3846 navType = GENERIC_NAV;
3847 pageTitle = NULL;
3848
3849 genericContext.genericContents.contentsList = localContentsList;
3850 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3851
3852 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3853 localContentsList[offset].type = TAG_VALUE_LIST;
3854 memcpy(&localContentsList[offset].content.tagValueList,
3855 tagValueList,
3857 offset++;
3858 }
3859
3860 localContentsList[offset].type = INFO_BUTTON;
3861 localContentsList[offset].content.infoButton.text = infoLongPress->text;
3862 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
3863 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
3864 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
3865 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
3866 offset++;
3867
3868 genericContext.genericContents.nbContents = offset;
3869
3870 // compute number of pages & fill navigation structure
3871 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3872 UNUSED(rejectText);
3873 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3874
3875 displayGenericContextPage(0, true);
3876}
3877
3894void nbgl_useCaseReview(nbgl_operationType_t operationType,
3895 const nbgl_contentTagValueList_t *tagValueList,
3896 const nbgl_icon_details_t *icon,
3897 const char *reviewTitle,
3898 const char *reviewSubTitle,
3899 const char *finishTitle,
3900 nbgl_choiceCallback_t choiceCallback)
3901{
3902 useCaseReview(operationType,
3903 tagValueList,
3904 icon,
3905 reviewTitle,
3906 reviewSubTitle,
3907 finishTitle,
3908 NULL,
3909 choiceCallback,
3910 false,
3911 true);
3912}
3913
3934 const nbgl_contentTagValueList_t *tagValueList,
3935 const nbgl_icon_details_t *icon,
3936 const char *reviewTitle,
3937 const char *reviewSubTitle,
3938 const char *finishTitle,
3939 const nbgl_tipBox_t *tipBox,
3940 nbgl_choiceCallback_t choiceCallback)
3941{
3942 nbgl_useCaseAdvancedReview(operationType,
3943 tagValueList,
3944 icon,
3945 reviewTitle,
3946 reviewSubTitle,
3947 finishTitle,
3948 tipBox,
3949 &blindSigningWarning,
3950 choiceCallback);
3951}
3952
3976 const nbgl_contentTagValueList_t *tagValueList,
3977 const nbgl_icon_details_t *icon,
3978 const char *reviewTitle,
3979 const char *reviewSubTitle,
3980 const char *finishTitle,
3981 const nbgl_tipBox_t *tipBox,
3982 const nbgl_warning_t *warning,
3983 nbgl_choiceCallback_t choiceCallback)
3984{
3985 reset_callbacks_and_context();
3986 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
3987
3988 // memorize tipBox because it can be in the call stack of the caller
3989 if (tipBox != NULL) {
3990 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
3991 }
3992 // if no warning at all, it's a simple review
3993 if ((warning == NULL)
3994 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
3995 && (warning->reviewDetails == NULL))) {
3996 useCaseReview(operationType,
3997 tagValueList,
3998 icon,
3999 reviewTitle,
4000 reviewSubTitle,
4001 finishTitle,
4002 tipBox,
4003 choiceCallback,
4004 false,
4005 true);
4006 return;
4007 }
4008 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4009 operationType |= NO_THREAT_OPERATION;
4010 }
4011 else {
4012 operationType |= RISKY_OPERATION;
4013 }
4014 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4015 reviewWithWarnCtx.isStreaming = false;
4016 reviewWithWarnCtx.operationType = operationType;
4017 reviewWithWarnCtx.tagValueList = tagValueList;
4018 reviewWithWarnCtx.icon = icon;
4019 reviewWithWarnCtx.reviewTitle = reviewTitle;
4020 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4021 reviewWithWarnCtx.finishTitle = finishTitle;
4022 reviewWithWarnCtx.warning = warning;
4023 reviewWithWarnCtx.choiceCallback = choiceCallback;
4024
4025 // display the initial warning only of a risk/threat or blind signing
4026 if ((!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
4027 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
4028 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)))
4029 && (warning->introDetails == NULL)) {
4030 useCaseReview(operationType,
4031 tagValueList,
4032 icon,
4033 reviewTitle,
4034 reviewSubTitle,
4035 finishTitle,
4036 tipBox,
4037 choiceCallback,
4038 false,
4039 true);
4040 return;
4041 }
4042
4043 displayInitialWarning();
4044}
4045
4063 const nbgl_contentTagValueList_t *tagValueList,
4064 const nbgl_icon_details_t *icon,
4065 const char *reviewTitle,
4066 const char *reviewSubTitle,
4067 const char *finishTitle,
4068 nbgl_choiceCallback_t choiceCallback)
4069{
4070 useCaseReview(operationType,
4071 tagValueList,
4072 icon,
4073 reviewTitle,
4074 reviewSubTitle,
4075 finishTitle,
4076 NULL,
4077 choiceCallback,
4078 true,
4079 true);
4080}
4081
4091 const char *rejectText,
4092 nbgl_callback_t rejectCallback)
4093{
4094 reset_callbacks_and_context();
4095
4096 // memorize context
4097 onQuit = rejectCallback;
4098 navType = GENERIC_NAV;
4099 pageTitle = NULL;
4100 bundleNavContext.review.operationType = TYPE_OPERATION;
4101
4102 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
4103
4104 // compute number of pages & fill navigation structure
4105 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4106 prepareNavInfo(true, nbPages, rejectText);
4107 navInfo.quitToken = QUIT_TOKEN;
4108
4109#ifdef HAVE_PIEZO_SOUND
4110 // Play notification sound
4111 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4112#endif // HAVE_PIEZO_SOUND
4113
4114 displayGenericContextPage(0, true);
4115}
4116
4130 const nbgl_icon_details_t *icon,
4131 const char *reviewTitle,
4132 const char *reviewSubTitle,
4133 nbgl_choiceCallback_t choiceCallback)
4134{
4135 useCaseReviewStreamingStart(
4136 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4137}
4138
4153 const nbgl_icon_details_t *icon,
4154 const char *reviewTitle,
4155 const char *reviewSubTitle,
4156 nbgl_choiceCallback_t choiceCallback)
4157{
4159 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4160}
4161
4178 const nbgl_icon_details_t *icon,
4179 const char *reviewTitle,
4180 const char *reviewSubTitle,
4181 const nbgl_warning_t *warning,
4182 nbgl_choiceCallback_t choiceCallback)
4183{
4184 memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
4185 // if no warning at all, it's a simple review
4186 if ((warning == NULL)
4187 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4188 && (warning->reviewDetails == NULL))) {
4189 useCaseReviewStreamingStart(
4190 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4191 return;
4192 }
4193 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4194 operationType |= NO_THREAT_OPERATION;
4195 }
4196 else {
4197 operationType |= RISKY_OPERATION;
4198 }
4199
4200 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4201 reviewWithWarnCtx.isStreaming = true;
4202 reviewWithWarnCtx.operationType = operationType;
4203 reviewWithWarnCtx.icon = icon;
4204 reviewWithWarnCtx.reviewTitle = reviewTitle;
4205 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4206 reviewWithWarnCtx.choiceCallback = choiceCallback;
4207 reviewWithWarnCtx.warning = warning;
4208
4209 // display the initial warning only of a risk/threat or blind signing
4210 if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
4211 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
4212 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
4213 useCaseReviewStreamingStart(
4214 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4215 return;
4216 }
4217 displayInitialWarning();
4218}
4219
4234 nbgl_choiceCallback_t choiceCallback,
4235 nbgl_callback_t skipCallback)
4236{
4237 // Should follow a call to nbgl_useCaseReviewStreamingStart
4238 memset(&genericContext, 0, sizeof(genericContext));
4239
4240 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4241 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4242
4243 // memorize context
4244 onChoice = bundleNavReviewStreamingChoice;
4245 navType = STREAMING_NAV;
4246 pageTitle = NULL;
4247
4248 genericContext.genericContents.contentsList = localContentsList;
4249 genericContext.genericContents.nbContents = 1;
4250 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4251
4252 // Then the tag/value pairs
4253 STARTING_CONTENT.type = TAG_VALUE_LIST;
4254 memcpy(
4255 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4256
4257 // compute number of pages & fill navigation structure
4258 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4259 &genericContext.genericContents,
4260 0,
4261 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4262 prepareNavInfo(true,
4264 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4265 // if the operation is skippable
4266 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4267 navInfo.progressIndicator = false;
4268 navInfo.skipText = "Skip";
4269 navInfo.skipToken = SKIP_TOKEN;
4270 }
4271
4272 displayGenericContextPage(0, true);
4273}
4274
4286 nbgl_choiceCallback_t choiceCallback)
4287{
4288 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4289}
4290
4299void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4300 nbgl_choiceCallback_t choiceCallback)
4301{
4302 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4303 memset(&genericContext, 0, sizeof(genericContext));
4304
4305 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4306
4307 // memorize context
4308 onChoice = bundleNavReviewStreamingChoice;
4309 navType = STREAMING_NAV;
4310 pageTitle = NULL;
4311
4312 genericContext.genericContents.contentsList = localContentsList;
4313 genericContext.genericContents.nbContents = 1;
4314 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4315
4316 // Eventually the long press page
4317 STARTING_CONTENT.type = INFO_LONG_PRESS;
4318 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4319 &STARTING_CONTENT.content.infoLongPress,
4320 bundleNavContext.reviewStreaming.icon,
4321 finishTitle);
4322
4323 // compute number of pages & fill navigation structure
4324 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4325 &genericContext.genericContents,
4326 0,
4327 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4328 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4329
4330 displayGenericContextPage(0, true);
4331}
4332
4337void nbgl_useCaseAddressConfirmationExt(const char *address,
4338 nbgl_choiceCallback_t callback,
4339 const nbgl_contentTagValueList_t *tagValueList)
4340{
4341 reset_callbacks_and_context();
4342 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4343
4344 // save context
4345 onChoice = callback;
4346 navType = GENERIC_NAV;
4347 pageTitle = NULL;
4348
4349 genericContext.genericContents.contentsList = localContentsList;
4350 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4351 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4352 prepareAddressConfirmationPages(
4353 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4354
4355 // fill navigation structure, common to all pages
4356 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4357
4358 prepareNavInfo(true, nbPages, "Cancel");
4359
4360#ifdef HAVE_PIEZO_SOUND
4361 // Play notification sound
4362 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4363#endif // HAVE_PIEZO_SOUND
4364
4365 displayGenericContextPage(0, true);
4366}
4367
4384void nbgl_useCaseAddressReview(const char *address,
4385 const nbgl_contentTagValueList_t *additionalTagValueList,
4386 const nbgl_icon_details_t *icon,
4387 const char *reviewTitle,
4388 const char *reviewSubTitle,
4389 nbgl_choiceCallback_t choiceCallback)
4390{
4391 reset_callbacks_and_context();
4392
4393 // release a potential modal
4394 if (addressConfirmationContext.modalLayout) {
4395 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4396 }
4397 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4398
4399 // save context
4400 onChoice = choiceCallback;
4401 navType = GENERIC_NAV;
4402 pageTitle = NULL;
4403 bundleNavContext.review.operationType = TYPE_OPERATION;
4404
4405 genericContext.genericContents.contentsList = localContentsList;
4406 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4407
4408 // First a centered info
4409 STARTING_CONTENT.type = EXTENDED_CENTER;
4410 prepareReviewFirstPage(
4411 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4412 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4413
4414 // Then the address confirmation pages
4415 prepareAddressConfirmationPages(
4416 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4417
4418 // fill navigation structure, common to all pages
4419 genericContext.genericContents.nbContents
4420 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4421 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4422
4423 prepareNavInfo(true, nbPages, "Cancel");
4424
4425#ifdef HAVE_PIEZO_SOUND
4426 // Play notification sound
4427 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4428#endif // HAVE_PIEZO_SOUND
4429
4430 displayGenericContextPage(0, true);
4431}
4432
4441void nbgl_useCaseSpinner(const char *text)
4442{
4443 // if the previous Use Case was not Spinner, fresh start
4444 if (genericContext.type != USE_CASE_SPINNER) {
4445 memset(&genericContext, 0, sizeof(genericContext));
4446 genericContext.type = USE_CASE_SPINNER;
4447 nbgl_layoutDescription_t layoutDescription = {0};
4448
4449 layoutDescription.withLeftBorder = true;
4450
4451 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4452
4454 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4455
4456 nbgl_layoutDraw(genericContext.backgroundLayout);
4458 }
4459 else {
4460 // otherwise increment spinner
4461 genericContext.spinnerPosition++;
4462 // there are only NB_SPINNER_POSITIONSpositions
4463 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4464 genericContext.spinnerPosition = 0;
4465 }
4466 int ret = nbgl_layoutUpdateSpinner(
4467 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4468 if (ret == 1) {
4470 }
4471 else if (ret == 2) {
4473 }
4474 }
4475}
4476
4477#ifdef NBGL_KEYPAD
4497void nbgl_useCaseKeypadDigits(const char *title,
4498 uint8_t minDigits,
4499 uint8_t maxDigits,
4500 uint8_t backToken,
4501 bool shuffled,
4502 tune_index_e tuneId,
4503 nbgl_pinValidCallback_t validatePinCallback,
4504 nbgl_layoutTouchCallback_t actionCallback)
4505{
4506 keypadGenericUseCase(title,
4507 minDigits,
4508 maxDigits,
4509 backToken,
4510 shuffled,
4511 false,
4512 tuneId,
4513 validatePinCallback,
4514 actionCallback);
4515}
4535void nbgl_useCaseKeypadPIN(const char *title,
4536 uint8_t minDigits,
4537 uint8_t maxDigits,
4538 uint8_t backToken,
4539 bool shuffled,
4540 tune_index_e tuneId,
4541 nbgl_pinValidCallback_t validatePinCallback,
4542 nbgl_layoutTouchCallback_t actionCallback)
4543{
4544 keypadGenericUseCase(title,
4545 minDigits,
4546 maxDigits,
4547 backToken,
4548 shuffled,
4549 true,
4550 tuneId,
4551 validatePinCallback,
4552 actionCallback);
4553}
4554#endif // NBGL_KEYPAD
4555
4556#endif // HAVE_SE_TOUCH
4557#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:1697
#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:1707
#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:1723
#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:324
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_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
uint8_t confirmationToken
the token used as argument of the onActionCallback
Definition nbgl_page.h:153
const char * confirmationText
text of the confirmation button
Definition nbgl_page.h:151
nbgl_layoutCenteredInfo_t centeredInfo
description of the centered info to be used
Definition nbgl_page.h:150
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
tune_index_e tuneId
if not NBGL_NO_TUNE, a tune will be played when button/footer is pressed
Definition nbgl_page.h:204
const nbgl_icon_details_t * actionButtonIcon
potential icon of "action" button
Definition nbgl_page.h:201
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 * 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