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 sharedContext genericContext.sharedCtx
57#define keypadContext sharedContext.keypad
58#define reviewWithWarnCtx sharedContext.reviewWithWarning
59#define choiceWithDetailsCtx sharedContext.choiceWithDetails
60
61/* max length of the string displaying the description of the Web3 Checks report */
62#define W3C_DESCRIPTION_MAX_LEN 128
63
69#define RISKY_OPERATION (1 << 6)
70
76#define NO_THREAT_OPERATION (1 << 7)
77
78/**********************
79 * TYPEDEFS
80 **********************/
81enum {
82 BACK_TOKEN = 0,
83 NEXT_TOKEN,
84 QUIT_TOKEN,
85 NAV_TOKEN,
86 MODAL_NAV_TOKEN,
87 SKIP_TOKEN,
88 CONTINUE_TOKEN,
89 ADDRESS_QRCODE_BUTTON_TOKEN,
90 ACTION_BUTTON_TOKEN,
91 CHOICE_TOKEN,
92 DETAILS_BUTTON_TOKEN,
93 CONFIRM_TOKEN,
94 REJECT_TOKEN,
95 VALUE_ALIAS_TOKEN,
96 INFO_ALIAS_TOKEN,
97 INFOS_TIP_BOX_TOKEN,
98 BLIND_WARNING_TOKEN,
99 WARNING_BUTTON_TOKEN,
100 CHOICE_DETAILS_TOKEN,
101 TIP_BOX_TOKEN,
102 QUIT_TIPBOX_MODAL_TOKEN,
103 WARNING_CHOICE_TOKEN,
104 DISMISS_QR_TOKEN,
105 DISMISS_WARNING_TOKEN,
106 DISMISS_DETAILS_TOKEN,
107 FIRST_WARN_BAR_TOKEN,
108 LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
109};
110
111typedef enum {
112 REVIEW_NAV = 0,
113 SETTINGS_NAV,
114 GENERIC_NAV,
115 STREAMING_NAV
116} NavType_t;
117
118typedef struct DetailsContext_s {
119 uint8_t nbPages;
120 uint8_t currentPage;
121 uint8_t currentPairIdx;
122 bool wrapping;
123 const char *tag;
124 const char *value;
125 const char *nextPageStart;
126} DetailsContext_t;
127
128typedef struct AddressConfirmationContext_s {
129 nbgl_layoutTagValue_t tagValuePairs[ADDR_VERIF_NB_PAIRS];
130 nbgl_layout_t *modalLayout;
131 uint8_t nbPairs;
132} AddressConfirmationContext_t;
133
134#ifdef NBGL_KEYPAD
135typedef struct KeypadContext_s {
136 uint8_t pinEntry[KEYPAD_MAX_DIGITS];
137 uint8_t pinLen;
138 uint8_t pinMinDigits;
139 uint8_t pinMaxDigits;
140 nbgl_layout_t *layoutCtx;
141 bool hidden;
142} KeypadContext_t;
143#endif
144
145typedef struct ReviewWithWarningContext_s {
146 bool isStreaming;
147 nbgl_operationType_t operationType;
148 const nbgl_contentTagValueList_t *tagValueList;
149 const nbgl_icon_details_t *icon;
150 const char *reviewTitle;
151 const char *reviewSubTitle;
152 const char *finishTitle;
153 const nbgl_warning_t *warning;
154 nbgl_choiceCallback_t choiceCallback;
155 nbgl_layout_t *layoutCtx;
156 nbgl_layout_t *modalLayout;
157 uint8_t securityReportLevel; // level 1 is the first level of menus
158 bool isIntro; // set to true during intro (before actual review)
159} ReviewWithWarningContext_t;
160
161typedef struct ChoiceWithDetailsContext_s {
162 const nbgl_genericDetails_t *details;
163 nbgl_layout_t *layoutCtx;
164 nbgl_layout_t *modalLayout;
165 uint8_t level; // level 1 is the first level of menus
166} ChoiceWithDetailsContext_t;
167
168typedef enum {
169 SHARE_CTX_KEYPAD,
170 SHARE_CTX_REVIEW_WITH_WARNING,
171 SHARE_CTX_CHOICE_WITH_DETAILS,
172} SharedContextUsage_t;
173
174// this union is intended to save RAM for context storage
175// indeed, these 3 contexts cannot happen simultaneously
176typedef struct {
177 union {
178#ifdef NBGL_KEYPAD
179 KeypadContext_t keypad;
180#endif
181 ReviewWithWarningContext_t reviewWithWarning;
182 ChoiceWithDetailsContext_t choiceWithDetails;
183 };
184 SharedContextUsage_t usage;
185} SharedContext_t;
186
187typedef enum {
188 USE_CASE_GENERIC = 0,
189 USE_CASE_SPINNER
190} GenericContextType_t;
191
192typedef struct {
193 GenericContextType_t type; // type of Generic context usage
194 uint8_t spinnerPosition;
195 nbgl_genericContents_t genericContents;
196 int8_t currentContentIdx;
197 uint8_t currentContentElementNb;
198 uint8_t currentElementIdx;
199 bool hasStartingContent;
200 bool hasFinishingContent;
201 const char *detailsItem;
202 const char *detailsvalue;
203 bool detailsWrapping;
204 bool validWarningCtx; // set to true if the WarningContext is valid
206 *currentPairs; // to be used to retrieve the pairs with value alias
208 currentCallback; // to be used to retrieve the pairs with value alias
209 nbgl_layout_t modalLayout;
210 nbgl_layout_t backgroundLayout;
211 const nbgl_contentInfoList_t *currentInfos;
212 const nbgl_contentTagValueList_t *currentTagValues;
213 nbgl_tipBox_t tipBox;
214 SharedContext_t sharedCtx; // multi-purpose context shared for non-concurrent usages
215} GenericContext_t;
216
217typedef struct {
218 const char *appName;
219 const nbgl_icon_details_t *appIcon;
220 const char *tagline;
221 const nbgl_genericContents_t *settingContents;
222 const nbgl_contentInfoList_t *infosList;
223 nbgl_homeAction_t homeAction;
224 nbgl_callback_t quitCallback;
225} nbgl_homeAndSettingsContext_t;
226
227typedef struct {
228 nbgl_operationType_t operationType;
229 nbgl_choiceCallback_t choiceCallback;
230} nbgl_reviewContext_t;
231
232typedef struct {
233 nbgl_operationType_t operationType;
234 nbgl_choiceCallback_t choiceCallback;
235 nbgl_callback_t skipCallback;
236 const nbgl_icon_details_t *icon;
237 uint8_t stepPageNb;
238} nbgl_reviewStreamingContext_t;
239
240typedef union {
241 nbgl_homeAndSettingsContext_t homeAndSettings;
242 nbgl_reviewContext_t review;
243 nbgl_reviewStreamingContext_t reviewStreaming;
244} nbgl_BundleNavContext_t;
245
246typedef struct {
247 const nbgl_icon_details_t *icon;
248 const char *text;
249 const char *subText;
250} SecurityReportItem_t;
251
252/**********************
253 * STATIC VARIABLES
254 **********************/
255
256// char buffers to build some strings
257static char tmpString[W3C_DESCRIPTION_MAX_LEN];
258
259// multi-purposes callbacks
260static nbgl_callback_t onQuit;
261static nbgl_callback_t onContinue;
262static nbgl_callback_t onAction;
263static nbgl_navCallback_t onNav;
264static nbgl_layoutTouchCallback_t onControls;
265static nbgl_contentActionCallback_t onContentAction;
266static nbgl_choiceCallback_t onChoice;
267static nbgl_callback_t onModalConfirm;
268#ifdef NBGL_KEYPAD
269static nbgl_pinValidCallback_t onValidatePin;
270#endif
271
272// contexts for background and modal pages
273static nbgl_page_t *pageContext;
274static nbgl_page_t *modalPageContext;
275
276// context for pages
277static const char *pageTitle;
278
279// context for tip-box
280#define activeTipBox genericContext.tipBox
281
282// context for navigation use case
283static nbgl_pageNavigationInfo_t navInfo;
284static bool forwardNavOnly;
285static NavType_t navType;
286
287static DetailsContext_t detailsContext;
288
289// context for address review
290static AddressConfirmationContext_t addressConfirmationContext;
291
292// contexts for generic navigation
293static GenericContext_t genericContext;
294static nbgl_content_t
295 localContentsList[3]; // 3 needed for nbgl_useCaseReview (starting page / tags / final page)
296static uint8_t genericContextPagesInfo[MAX_PAGE_NB / PAGES_PER_UINT8];
297static uint8_t modalContextPagesInfo[MAX_MODAL_PAGE_NB / PAGES_PER_UINT8];
298
299// contexts for bundle navigation
300static nbgl_BundleNavContext_t bundleNavContext;
301
302// indexed by nbgl_contentType_t
303static const uint8_t nbMaxElementsPerContentType[] = {
304 1, // CENTERED_INFO
305 1, // EXTENDED_CENTER
306 1, // INFO_LONG_PRESS
307 1, // INFO_BUTTON
308 1, // TAG_VALUE_LIST (computed dynamically)
309 1, // TAG_VALUE_DETAILS
310 1, // TAG_VALUE_CONFIRM
311 3, // SWITCHES_LIST (computed dynamically)
312 3, // INFOS_LIST (computed dynamically)
313 5, // CHOICES_LIST (computed dynamically)
314 5, // BARS_LIST (computed dynamically)
315};
316
317// clang-format off
318static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
320 .icon = &WARNING_ICON,
321 .text = "Blind signing required",
322 .subText = "This transaction's details are not fully verifiable. If "
323 "you sign, you could lose all your assets."
324 },
325 [W3C_ISSUE_WARN] = {
326 .icon = &WARNING_ICON,
327 .text = "Transaction Check unavailable",
328 .subText = NULL
329 },
331 .icon = &WARNING_ICON,
332 .text = "Risk detected",
333 .subText = "This transaction was scanned as risky by Web3 Checks."
334 },
336 .icon = &WARNING_ICON,
337 .text = "Critical threat",
338 .subText = "This transaction was scanned as malicious by Web3 Checks."
339 },
341 .icon = NULL,
342 .text = "No threat detected",
343 .subText = "Transaction Check didn't find any threat, but always "
344 "review transaction details carefully."
345 }
346};
347// clang-format on
348
349// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
350static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
351
352#ifdef NBGL_QRCODE
353/* buffer to store reduced address under QR Code */
354static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
355#endif // NBGL_QRCODE
356
357/**********************
358 * STATIC FUNCTIONS
359 **********************/
360static void displayReviewPage(uint8_t page, bool forceFullRefresh);
361static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
362static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh);
363static void displayFullValuePage(const char *backText,
364 const char *aliasText,
365 const nbgl_contentValueExt_t *extension);
366static void displayInfosListModal(const char *modalTitle,
367 const nbgl_contentInfoList_t *infos,
368 bool fromReview);
369static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues);
370static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
371static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
372static void pageCallback(int token, uint8_t index);
373#ifdef NBGL_QRCODE
374static void displayAddressQRCode(void);
375#endif // NBGL_QRCODE
376static void modalLayoutTouchCallback(int token, uint8_t index);
377static void displaySkipWarning(void);
378
379static void bundleNavStartHome(void);
380static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
381static void bundleNavStartSettings(void);
382
383static void bundleNavReviewStreamingChoice(bool confirm);
384static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token);
385static void displaySecurityReport(uint32_t set);
386static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
387static void displayInitialWarning(void);
388static void useCaseReview(nbgl_operationType_t operationType,
389 const nbgl_contentTagValueList_t *tagValueList,
390 const nbgl_icon_details_t *icon,
391 const char *reviewTitle,
392 const char *reviewSubTitle,
393 const char *finishTitle,
394 const nbgl_tipBox_t *tipBox,
395 nbgl_choiceCallback_t choiceCallback,
396 bool isLight,
397 bool playNotifSound);
398static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
399 const nbgl_icon_details_t *icon,
400 const char *reviewTitle,
401 const char *reviewSubTitle,
402 nbgl_choiceCallback_t choiceCallback,
403 bool playNotifSound);
404static void useCaseHomeExt(const char *appName,
405 const nbgl_icon_details_t *appIcon,
406 const char *tagline,
407 bool withSettings,
408 nbgl_homeAction_t *homeAction,
409 nbgl_callback_t topRightCallback,
410 nbgl_callback_t quitCallback);
411static void displayDetails(const char *tag, const char *value, bool wrapping);
412
413static void reset_callbacks_and_context(void)
414{
415 onQuit = NULL;
416 onContinue = NULL;
417 onAction = NULL;
418 onNav = NULL;
419 onControls = NULL;
420 onContentAction = NULL;
421 onChoice = NULL;
422#ifdef NBGL_KEYPAD
423 onValidatePin = NULL;
424#endif
425 memset(&genericContext, 0, sizeof(genericContext));
426}
427
428// Helper to set genericContext page info
429static void genericContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements, bool flag)
430{
431 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements) + SET_PAGE_FLAG(flag);
432
433 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
434 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
435 genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
436 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
437}
438
439// Helper to get genericContext page info
440static void genericContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements, bool *flag)
441{
442 uint8_t pageData = genericContextPagesInfo[pageIdx / PAGES_PER_UINT8]
443 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
444 if (nbElements != NULL) {
445 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
446 }
447 if (flag != NULL) {
448 *flag = GET_PAGE_FLAG(pageData);
449 }
450}
451
452// Helper to set modalContext page info
453static void modalContextSetPageInfo(uint8_t pageIdx, uint8_t nbElements)
454{
455 uint8_t pageData = SET_PAGE_NB_ELEMENTS(nbElements);
456
457 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
458 &= ~(0x0F << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS));
459 modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
460 |= pageData << ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
461}
462
463// Helper to get modalContext page info
464static void modalContextGetPageInfo(uint8_t pageIdx, uint8_t *nbElements)
465{
466 uint8_t pageData = modalContextPagesInfo[pageIdx / PAGES_PER_UINT8]
467 >> ((pageIdx % PAGES_PER_UINT8) * PAGE_DATA_BITS);
468 if (nbElements != NULL) {
469 *nbElements = GET_PAGE_NB_ELEMENTS(pageData);
470 }
471}
472
473// Simple helper to get the number of elements inside a nbgl_content_t
474static uint8_t getContentNbElement(const nbgl_content_t *content)
475{
476 switch (content->type) {
477 case TAG_VALUE_LIST:
478 return content->content.tagValueList.nbPairs;
481 case SWITCHES_LIST:
482 return content->content.switchesList.nbSwitches;
483 case INFOS_LIST:
484 return content->content.infosList.nbInfos;
485 case CHOICES_LIST:
486 return content->content.choicesList.nbChoices;
487 case BARS_LIST:
488 return content->content.barsList.nbBars;
489 default:
490 return 1;
491 }
492}
493
494// Helper to retrieve the content inside a nbgl_genericContents_t using
495// either the contentsList or using the contentGetterCallback
496static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *genericContents,
497 int8_t contentIdx,
498 nbgl_content_t *content)
499{
500 if (contentIdx < 0 || contentIdx >= genericContents->nbContents) {
501 LOG_DEBUG(USE_CASE_LOGGER, "No content available at %d\n", contentIdx);
502 return NULL;
503 }
504
505 if (genericContents->callbackCallNeeded) {
506 // Retrieve content through callback, but first memset the content.
507 memset(content, 0, sizeof(nbgl_content_t));
508 genericContents->contentGetterCallback(contentIdx, content);
509 return content;
510 }
511 else {
512 // Retrieve content through list
513 return PIC(&genericContents->contentsList[contentIdx]);
514 }
515}
516
517static void prepareNavInfo(bool isReview, uint8_t nbPages, const char *rejectText)
518{
519 memset(&navInfo, 0, sizeof(navInfo));
520
521 navInfo.nbPages = nbPages;
522 navInfo.tuneId = TUNE_TAP_CASUAL;
523 navInfo.progressIndicator = false;
524 navInfo.navType = NAV_WITH_BUTTONS;
525
526 if (isReview == false) {
527 navInfo.navWithButtons.navToken = NAV_TOKEN;
528 navInfo.navWithButtons.backButton = true;
529 }
530 else {
531 navInfo.quitToken = REJECT_TOKEN;
532 navInfo.navWithButtons.quitText = rejectText;
533 navInfo.navWithButtons.navToken = NAV_TOKEN;
535 = ((navType == STREAMING_NAV) && (nbPages < 2)) ? false : true;
536 navInfo.navWithButtons.visiblePageIndicator = (navType != STREAMING_NAV);
537 }
538}
539
540static void prepareReviewFirstPage(nbgl_contentCenter_t *contentCenter,
541 const nbgl_icon_details_t *icon,
542 const char *reviewTitle,
543 const char *reviewSubTitle)
544{
545 contentCenter->icon = icon;
546 contentCenter->title = reviewTitle;
547 contentCenter->description = reviewSubTitle;
548 contentCenter->subText = "Swipe to review";
549 contentCenter->smallTitle = NULL;
550 contentCenter->iconHug = 0;
551 contentCenter->padding = false;
552 contentCenter->illustrType = ICON_ILLUSTRATION;
553}
554
555static const char *getFinishTitle(nbgl_operationType_t operationType, const char *finishTitle)
556{
557 if (finishTitle != NULL) {
558 return finishTitle;
559 }
560 switch (operationType & REAL_TYPE_MASK) {
561 case TYPE_TRANSACTION:
562 return "Sign transaction";
563 case TYPE_MESSAGE:
564 return "Sign message";
565 default:
566 return "Sign operation";
567 }
568}
569
570static void prepareReviewLastPage(nbgl_operationType_t operationType,
571 nbgl_contentInfoLongPress_t *infoLongPress,
572 const nbgl_icon_details_t *icon,
573 const char *finishTitle)
574{
575 infoLongPress->text = getFinishTitle(operationType, finishTitle);
576 infoLongPress->icon = icon;
577 infoLongPress->longPressText = "Hold to sign";
578 infoLongPress->longPressToken = CONFIRM_TOKEN;
579}
580
581static void prepareReviewLightLastPage(nbgl_operationType_t operationType,
582 nbgl_contentInfoButton_t *infoButton,
583 const nbgl_icon_details_t *icon,
584 const char *finishTitle)
585{
586 infoButton->text = getFinishTitle(operationType, finishTitle);
587 infoButton->icon = icon;
588 infoButton->buttonText = "Approve";
589 infoButton->buttonToken = CONFIRM_TOKEN;
590}
591
592static const char *getRejectReviewText(nbgl_operationType_t operationType)
593{
594 UNUSED(operationType);
595 return "Reject";
596}
597
598// function called when navigating (or exiting) modal details pages
599// or when skip choice is displayed
600static void pageModalCallback(int token, uint8_t index)
601{
602 LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
603
604 if (token == INFOS_TIP_BOX_TOKEN) {
605 // the icon next to info type alias has been touched
606 displayFullValuePage(activeTipBox.infos.infoTypes[index],
607 activeTipBox.infos.infoContents[index],
608 &activeTipBox.infos.infoExtensions[index]);
609 return;
610 }
611 else if (token == INFO_ALIAS_TOKEN) {
612 // the icon next to info type alias has been touched, but coming from review
613 displayFullValuePage(genericContext.currentInfos->infoTypes[index],
614 genericContext.currentInfos->infoContents[index],
615 &genericContext.currentInfos->infoExtensions[index]);
616 return;
617 }
618 nbgl_pageRelease(modalPageContext);
619 modalPageContext = NULL;
620 if (token == NAV_TOKEN) {
621 if (index == EXIT_PAGE) {
622 // redraw the background layer
624 nbgl_refresh();
625 }
626 else {
627 displayDetailsPage(index, false);
628 }
629 }
630 if (token == MODAL_NAV_TOKEN) {
631 if (index == EXIT_PAGE) {
632 // redraw the background layer
634 nbgl_refresh();
635 }
636 else {
637 displayTagValueListModalPage(index, false);
638 }
639 }
640 else if (token == QUIT_TOKEN) {
641 // redraw the background layer
643 nbgl_refresh();
644 }
645 else if (token == QUIT_TIPBOX_MODAL_TOKEN) {
646 // if in "warning context", go back to security report
647 if (reviewWithWarnCtx.securityReportLevel == 2) {
648 reviewWithWarnCtx.securityReportLevel = 1;
649 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
650 }
651 else {
652 // redraw the background layer
654 nbgl_refresh();
655 }
656 }
657 else if (token == SKIP_TOKEN) {
658 if (index == 0) {
659 // display the last forward only review page, whatever it is
660 displayGenericContextPage(LAST_PAGE_FOR_REVIEW, true);
661 }
662 else {
663 // display background, which should be the page where skip has been touched
665 nbgl_refresh();
666 }
667 }
668 else if (token == CHOICE_TOKEN) {
669 if (index == 0) {
670 if (onModalConfirm != NULL) {
671 onModalConfirm();
672 onModalConfirm = NULL;
673 }
674 }
675 else {
676 // display background, which should be the page where skip has been touched
678 nbgl_refresh();
679 }
680 }
681}
682
683// generic callback for all pages except modal
684static void pageCallback(int token, uint8_t index)
685{
686 LOG_DEBUG(USE_CASE_LOGGER, "pageCallback, token = %d, index = %d\n", token, index);
687 if (token == QUIT_TOKEN) {
688 if (onQuit != NULL) {
689 onQuit();
690 }
691 }
692 else if (token == CONTINUE_TOKEN) {
693 if (onContinue != NULL) {
694 onContinue();
695 }
696 }
697 else if (token == CHOICE_TOKEN) {
698 if (onChoice != NULL) {
699 onChoice((index == 0) ? true : false);
700 }
701 }
702 else if (token == ACTION_BUTTON_TOKEN) {
703 if ((index == 0) && (onAction != NULL)) {
704 onAction();
705 }
706 else if ((index == 1) && (onQuit != NULL)) {
707 onQuit();
708 }
709 }
710#ifdef NBGL_QRCODE
711 else if (token == ADDRESS_QRCODE_BUTTON_TOKEN) {
712 displayAddressQRCode();
713 }
714#endif // NBGL_QRCODE
715 else if (token == CONFIRM_TOKEN) {
716 if (onChoice != NULL) {
717 onChoice(true);
718 }
719 }
720 else if (token == REJECT_TOKEN) {
721 if (onChoice != NULL) {
722 onChoice(false);
723 }
724 }
725 else if (token == DETAILS_BUTTON_TOKEN) {
726 displayDetails(genericContext.detailsItem,
727 genericContext.detailsvalue,
728 genericContext.detailsWrapping);
729 }
730 else if (token == NAV_TOKEN) {
731 if (index == EXIT_PAGE) {
732 if (onQuit != NULL) {
733 onQuit();
734 }
735 }
736 else {
737 if (navType == GENERIC_NAV || navType == STREAMING_NAV) {
738 displayGenericContextPage(index, false);
739 }
740 else if (navType == REVIEW_NAV) {
741 displayReviewPage(index, false);
742 }
743 else {
744 displaySettingsPage(index, false);
745 }
746 }
747 }
748 else if (token == NEXT_TOKEN) {
749 if (onNav != NULL) {
750 displayReviewPage(navInfo.activePage + 1, false);
751 }
752 else {
753 displayGenericContextPage(navInfo.activePage + 1, false);
754 }
755 }
756 else if (token == BACK_TOKEN) {
757 if (onNav != NULL) {
758 displayReviewPage(navInfo.activePage - 1, true);
759 }
760 else {
761 displayGenericContextPage(navInfo.activePage - 1, false);
762 }
763 }
764 else if (token == SKIP_TOKEN) {
765 // display a modal warning to confirm skip
766 displaySkipWarning();
767 }
768 else if (token == VALUE_ALIAS_TOKEN) {
769 // the icon next to value alias has been touched
770 const nbgl_contentTagValue_t *pair;
771 if (genericContext.currentPairs != NULL) {
772 pair = &genericContext.currentPairs[genericContext.currentElementIdx + index];
773 }
774 else {
775 pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
776 }
777 displayFullValuePage(pair->item, pair->value, pair->extension);
778 }
779 else if (token == BLIND_WARNING_TOKEN) {
780 reviewWithWarnCtx.isIntro = false;
781 reviewWithWarnCtx.warning = NULL;
782 displaySecurityReport(1 << BLIND_SIGNING_WARN);
783 }
784 else if (token == WARNING_BUTTON_TOKEN) {
785 // top-right button, display the security modal
786 reviewWithWarnCtx.securityReportLevel = 1;
787 // if preset is used
788 if (reviewWithWarnCtx.warning->predefinedSet) {
789 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
790 }
791 else {
792 // use customized warning
793 if (reviewWithWarnCtx.isIntro) {
794 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
795 }
796 else {
797 displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
798 }
799 }
800 }
801 else if (token == TIP_BOX_TOKEN) {
802 // if warning context is valid and if W3C directly display same as top-right
803 if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) {
804 reviewWithWarnCtx.securityReportLevel = 1;
805 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
806 }
807 else {
808 displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false);
809 }
810 }
811 else { // probably a control provided by caller
812 if (onContentAction != NULL) {
813 onContentAction(token, index, navInfo.activePage);
814 }
815 if (onControls != NULL) {
816 onControls(token, index);
817 }
818 }
819}
820
821// callback used for confirmation
822static void tickerCallback(void)
823{
824 nbgl_pageRelease(pageContext);
825 if (onQuit != NULL) {
826 onQuit();
827 }
828}
829
830// function used to display the current page in review
831static void displaySettingsPage(uint8_t page, bool forceFullRefresh)
832{
833 nbgl_pageContent_t content = {0};
834
835 if ((onNav == NULL) || (onNav(page, &content) == false)) {
836 return;
837 }
838
839 // override some fields
840 content.title = pageTitle;
841 content.isTouchableTitle = true;
842 content.titleToken = QUIT_TOKEN;
843 content.tuneId = TUNE_TAP_CASUAL;
844
845 navInfo.activePage = page;
846 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &content);
847
848 if (forceFullRefresh) {
850 }
851 else {
853 }
854}
855
856// function used to display the current page in review
857static void displayReviewPage(uint8_t page, bool forceFullRefresh)
858{
859 nbgl_pageContent_t content = {0};
860
861 // ensure the page is valid
862 if ((navInfo.nbPages != 0) && (page >= (navInfo.nbPages))) {
863 return;
864 }
865 navInfo.activePage = page;
866 if ((onNav == NULL) || (onNav(navInfo.activePage, &content) == false)) {
867 return;
868 }
869
870 // override some fields
871 content.title = NULL;
872 content.isTouchableTitle = false;
873 content.tuneId = TUNE_TAP_CASUAL;
874
875 if (content.type == INFO_LONG_PRESS) { // last page
876 // for forward only review without known length...
877 // if we don't do that we cannot remove the '>' in the navigation bar at the last page
878 navInfo.nbPages = navInfo.activePage + 1;
879 content.infoLongPress.longPressToken = CONFIRM_TOKEN;
880 if (forwardNavOnly) {
881 // remove the "Skip" button
882 navInfo.skipText = NULL;
883 }
884 }
885
886 // override smallCaseForValue for tag/value types to false
887 if (content.type == TAG_VALUE_DETAILS) {
889 // the maximum displayable number of lines for value is NB_MAX_LINES_IN_REVIEW (without More
890 // 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->hideEndOfLastLine = true;
1131 p_tagValueList->wrapping = p_content->content.tagValueList.wrapping;
1132 }
1133
1134 break;
1135 }
1136 case TAG_VALUE_CONFIRM: {
1137 nbgl_contentTagValueList_t *p_tagValueList;
1138 // only display a TAG_VALUE_CONFIRM if we are at the last page
1139 if ((nextElementIdx + nbElementsInPage)
1141 memcpy(&pageContent->tagValueConfirm,
1142 &p_content->content.tagValueConfirm,
1143 sizeof(pageContent->tagValueConfirm));
1144 p_tagValueList = &pageContent->tagValueConfirm.tagValueList;
1145 }
1146 else {
1147 // else display it as a TAG_VALUE_LIST
1148 pageContent->type = TAG_VALUE_LIST;
1149 p_tagValueList = &pageContent->tagValueList;
1150 }
1151 p_tagValueList->nbPairs = nbElementsInPage;
1152 p_tagValueList->pairs
1153 = PIC(&p_content->content.tagValueConfirm.tagValueList.pairs[nextElementIdx]);
1154 // parse pairs to check if any contains an alias for value
1155 for (uint8_t i = 0; i < nbElementsInPage; i++) {
1156 if (p_tagValueList->pairs[i].aliasValue) {
1157 p_tagValueList->token = VALUE_ALIAS_TOKEN;
1158 break;
1159 }
1160 }
1161 // memorize pairs (or callback) for usage when alias is used
1162 genericContext.currentPairs = p_tagValueList->pairs;
1163 genericContext.currentCallback = p_tagValueList->callback;
1164 break;
1165 }
1166 case SWITCHES_LIST:
1167 pageContent->switchesList.nbSwitches = nbElementsInPage;
1168 pageContent->switchesList.switches
1169 = PIC(&p_content->content.switchesList.switches[nextElementIdx]);
1170 break;
1171 case INFOS_LIST:
1172 pageContent->infosList.nbInfos = nbElementsInPage;
1173 pageContent->infosList.infoTypes
1174 = PIC(&p_content->content.infosList.infoTypes[nextElementIdx]);
1175 pageContent->infosList.infoContents
1176 = PIC(&p_content->content.infosList.infoContents[nextElementIdx]);
1177 break;
1178 case CHOICES_LIST:
1179 memcpy(&pageContent->choicesList,
1180 &p_content->content.choicesList,
1181 sizeof(pageContent->choicesList));
1182 pageContent->choicesList.nbChoices = nbElementsInPage;
1183 pageContent->choicesList.names
1184 = PIC(&p_content->content.choicesList.names[nextElementIdx]);
1185 if ((p_content->content.choicesList.initChoice >= nextElementIdx)
1186 && (p_content->content.choicesList.initChoice
1187 < nextElementIdx + nbElementsInPage)) {
1188 pageContent->choicesList.initChoice
1189 = p_content->content.choicesList.initChoice - nextElementIdx;
1190 }
1191 else {
1192 pageContent->choicesList.initChoice = nbElementsInPage;
1193 }
1194 break;
1195 case BARS_LIST:
1196 pageContent->barsList.nbBars = nbElementsInPage;
1197 pageContent->barsList.barTexts
1198 = PIC(&p_content->content.barsList.barTexts[nextElementIdx]);
1199 pageContent->barsList.tokens = PIC(&p_content->content.barsList.tokens[nextElementIdx]);
1200 pageContent->barsList.tuneId = p_content->content.barsList.tuneId;
1201 break;
1202 default:
1203 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported type %d\n", pageContent->type);
1204 return false;
1205 }
1206
1207 bool isFirstOrLastPage
1208 = ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
1209 || (p_content->type == INFO_LONG_PRESS);
1210 nbgl_operationType_t operationType
1211 = (navType == STREAMING_NAV)
1212 ? bundleNavContext.reviewStreaming.operationType
1213 : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
1214
1215 // if first or last page of review and blind/risky operation, add the warning top-right button
1216 if (isFirstOrLastPage
1217 && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) {
1218 // if issue is only Web3Checks "no threat", use privacy icon
1219 if ((operationType & NO_THREAT_OPERATION)
1220 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
1221 pageContent->topRightIcon = &PRIVACY_ICON;
1222 }
1223 else {
1224 pageContent->topRightIcon = &WARNING_ICON;
1225 }
1226
1227 pageContent->topRightToken
1228 = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
1229 }
1230
1231 return true;
1232}
1233
1234// function used to display the current page in generic context navigation mode
1235static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh)
1236{
1237 // Retrieve next page parameters
1238 nbgl_content_t content;
1239 uint8_t nbElementsInPage;
1240 bool flag;
1241 const nbgl_content_t *p_content = NULL;
1242
1243 if (navType == STREAMING_NAV) {
1244 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1245 if (bundleNavContext.reviewStreaming.skipCallback != NULL) {
1246 bundleNavContext.reviewStreaming.skipCallback();
1247 }
1248 return;
1249 }
1250 else if (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb) {
1251 bundleNavReviewStreamingChoice(true);
1252 return;
1253 }
1254 }
1255 else {
1256 // if coming from Skip modal, let's say we are at the last page
1257 if (pageIdx == LAST_PAGE_FOR_REVIEW) {
1258 pageIdx = navInfo.nbPages - 1;
1259 }
1260 }
1261
1262 if (navInfo.activePage == pageIdx) {
1263 p_content
1264 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1265 }
1266 else if (navInfo.activePage < pageIdx) {
1267 // Support going more than one step forward.
1268 // It occurs when initializing a navigation on an arbitrary page
1269 for (int i = navInfo.activePage + 1; i <= pageIdx; i++) {
1270 p_content = genericContextComputeNextPageParams(i, &content, &nbElementsInPage, &flag);
1271 }
1272 }
1273 else {
1274 if (pageIdx - navInfo.activePage > 1) {
1275 // We don't support going more than one step backward as it doesn't occurs for now?
1276 LOG_DEBUG(USE_CASE_LOGGER, "Unsupported navigation\n");
1277 return;
1278 }
1279 p_content
1280 = genericContextComputeNextPageParams(pageIdx, &content, &nbElementsInPage, &flag);
1281 }
1282
1283 if (p_content == NULL) {
1284 return;
1285 }
1286 // if the operation is skippable
1287 if ((navType != STREAMING_NAV)
1288 && (bundleNavContext.review.operationType & SKIPPABLE_OPERATION)) {
1289 // only present the "Skip" header on actual tag/value pages
1290 if ((pageIdx > 0) && (pageIdx < (navInfo.nbPages - 1))) {
1291 navInfo.progressIndicator = false;
1292 navInfo.skipText = "Skip";
1293 navInfo.skipToken = SKIP_TOKEN;
1294 }
1295 else {
1296 navInfo.skipText = NULL;
1297 }
1298 }
1299
1300 // Create next page content
1301 nbgl_pageContent_t pageContent = {0};
1302 if (!genericContextPreparePageContent(p_content, nbElementsInPage, flag, &pageContent)) {
1303 return;
1304 }
1305
1306 pageContext = nbgl_pageDrawGenericContent(&pageCallback, &navInfo, &pageContent);
1307
1308 if (forceFullRefresh) {
1310 }
1311 else {
1313 }
1314}
1315
1316// from the current details context, return a pointer on the details at the given page
1317static const char *getDetailsPageAt(uint8_t detailsPage)
1318{
1319 uint8_t page = 0;
1320 const char *currentChar = detailsContext.value;
1321 while (page < detailsPage) {
1322 uint16_t nbLines
1323 = nbgl_getTextNbLinesInWidth(SMALL_BOLD_FONT, currentChar, AVAILABLE_WIDTH, false);
1324 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1325 uint16_t len;
1326 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1327 currentChar,
1330 &len,
1331 detailsContext.wrapping);
1332 // don't start next page on a \n
1333 if (currentChar[len] == '\n') {
1334 len++;
1335 }
1336 else if (detailsContext.wrapping == false) {
1337 len -= 3;
1338 }
1339 currentChar = currentChar + len;
1340 }
1341 page++;
1342 }
1343 return currentChar;
1344}
1345
1346// function used to display the current page in details review mode
1347static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
1348{
1349 static nbgl_layoutTagValue_t currentPair;
1350 nbgl_pageNavigationInfo_t info = {.activePage = detailsPage,
1351 .nbPages = detailsContext.nbPages,
1352 .navType = NAV_WITH_BUTTONS,
1353 .quitToken = QUIT_TOKEN,
1354 .navWithButtons.navToken = NAV_TOKEN,
1355 .navWithButtons.quitButton = true,
1356 .navWithButtons.backButton = true,
1357 .navWithButtons.quitText = NULL,
1358 .progressIndicator = false,
1359 .tuneId = TUNE_TAP_CASUAL};
1361 .topRightIcon = NULL,
1362 .tagValueList.nbPairs = 1,
1363 .tagValueList.pairs = &currentPair,
1364 .tagValueList.smallCaseForValue = true,
1365 .tagValueList.wrapping = detailsContext.wrapping};
1366
1367 if (modalPageContext != NULL) {
1368 nbgl_pageRelease(modalPageContext);
1369 }
1370 currentPair.item = detailsContext.tag;
1371 // if move backward or first page
1372 if (detailsPage <= detailsContext.currentPage) {
1373 // recompute current start from beginning
1374 currentPair.value = getDetailsPageAt(detailsPage);
1375 forceFullRefresh = true;
1376 }
1377 // else move forward
1378 else {
1379 currentPair.value = detailsContext.nextPageStart;
1380 }
1381 detailsContext.currentPage = detailsPage;
1382 uint16_t nbLines = nbgl_getTextNbLinesInWidth(
1383 SMALL_BOLD_FONT, currentPair.value, AVAILABLE_WIDTH, detailsContext.wrapping);
1384
1385 if (nbLines > NB_MAX_LINES_IN_DETAILS) {
1386 uint16_t len;
1387 nbgl_getTextMaxLenInNbLines(SMALL_BOLD_FONT,
1388 currentPair.value,
1391 &len,
1392 detailsContext.wrapping);
1394 // don't start next page on a \n
1395 if (currentPair.value[len] == '\n') {
1396 len++;
1397 }
1398 else if (!detailsContext.wrapping) {
1400 len -= 3;
1401 }
1402 // memorize next position to save processing
1403 detailsContext.nextPageStart = currentPair.value + len;
1404 // use special feature to keep only NB_MAX_LINES_IN_DETAILS lines and replace the last 3
1405 // chars by "...", only if the next char to display in next page is not '\n'
1407 }
1408 else {
1409 detailsContext.nextPageStart = NULL;
1410 content.tagValueList.nbMaxLinesForValue = 0;
1411 }
1412 if (info.nbPages == 1) {
1413 // if only one page, no navigation bar, and use a footer instead
1414 info.navWithButtons.quitText = "Close";
1415 }
1416 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1417
1418 if (forceFullRefresh) {
1420 }
1421 else {
1423 }
1424}
1425
1426// function used to display the content of a full value, when touching an alias of a tag/value pair
1427static void displayFullValuePage(const char *backText,
1428 const char *aliasText,
1429 const nbgl_contentValueExt_t *extension)
1430{
1431 const char *modalTitle
1432 = (extension->backText != NULL) ? PIC(extension->backText) : PIC(backText);
1433 if (extension->aliasType == INFO_LIST_ALIAS) {
1434 genericContext.currentInfos = extension->infolist;
1435 displayInfosListModal(modalTitle, extension->infolist, true);
1436 }
1437 else if (extension->aliasType == TAG_VALUE_LIST_ALIAS) {
1438 genericContext.currentTagValues = extension->tagValuelist;
1439 displayTagValueListModal(extension->tagValuelist);
1440 }
1441 else {
1442 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1443 .withLeftBorder = true,
1444 .onActionCallback = &modalLayoutTouchCallback,
1445 .tapActionText = NULL};
1447 .separationLine = false,
1448 .backAndText.token = 0,
1449 .backAndText.tuneId = TUNE_TAP_CASUAL,
1450 .backAndText.text = modalTitle};
1451 genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1452 // add header with the tag part of the pair, to go back
1453 nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
1454 // add either QR Code or full value text
1455 if (extension->aliasType == QR_CODE_ALIAS) {
1456#ifdef NBGL_QRCODE
1457 nbgl_layoutQRCode_t qrCode
1458 = {.url = extension->fullValue,
1459 .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
1460 .text2 = extension->explanation,
1461 .centered = true,
1462 .offsetY = 0};
1463
1464 nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
1465#endif // NBGL_QRCODE
1466 }
1467 else {
1468 const char *info;
1469 // add full value text
1470 if (extension->aliasType == ENS_ALIAS) {
1471 info = "ENS names are resolved by Ledger backend.";
1472 }
1473 else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
1474 info = "This account label comes from your Address Book in Ledger Wallet.";
1475 }
1476 else {
1477 info = extension->explanation;
1478 }
1480 genericContext.modalLayout, aliasText, extension->fullValue, info);
1481 }
1482 // draw & refresh
1483 nbgl_layoutDraw(genericContext.modalLayout);
1484 nbgl_refresh();
1485 }
1486}
1487
1488// function used to display the modal containing tip-box infos
1489static void displayInfosListModal(const char *modalTitle,
1490 const nbgl_contentInfoList_t *infos,
1491 bool fromReview)
1492{
1494 .nbPages = 1,
1495 .navType = NAV_WITH_BUTTONS,
1496 .quitToken = QUIT_TIPBOX_MODAL_TOKEN,
1497 .navWithButtons.navToken = NAV_TOKEN,
1498 .navWithButtons.quitButton = false,
1499 .navWithButtons.backButton = true,
1500 .navWithButtons.quitText = NULL,
1501 .progressIndicator = false,
1502 .tuneId = TUNE_TAP_CASUAL};
1503 nbgl_pageContent_t content
1504 = {.type = INFOS_LIST,
1505 .topRightIcon = NULL,
1506 .infosList.nbInfos = infos->nbInfos,
1507 .infosList.withExtensions = infos->withExtensions,
1508 .infosList.infoTypes = infos->infoTypes,
1509 .infosList.infoContents = infos->infoContents,
1510 .infosList.infoExtensions = infos->infoExtensions,
1511 .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN,
1512 .title = modalTitle,
1513 .titleToken = QUIT_TIPBOX_MODAL_TOKEN,
1514 .tuneId = TUNE_TAP_CASUAL};
1515
1516 if (modalPageContext != NULL) {
1517 nbgl_pageRelease(modalPageContext);
1518 }
1519 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1520
1522}
1523
1524// function used to display the modal containing alias tag-value pairs
1525static void displayTagValueListModalPage(uint8_t pageIdx, bool forceFullRefresh)
1526{
1527 nbgl_pageNavigationInfo_t info = {.activePage = pageIdx,
1528 .nbPages = detailsContext.nbPages,
1529 .navType = NAV_WITH_BUTTONS,
1530 .quitToken = QUIT_TOKEN,
1531 .navWithButtons.navToken = MODAL_NAV_TOKEN,
1532 .navWithButtons.quitButton = true,
1533 .navWithButtons.backButton = true,
1534 .navWithButtons.quitText = NULL,
1535 .progressIndicator = false,
1536 .tuneId = TUNE_TAP_CASUAL};
1538 .topRightIcon = NULL,
1539 .tagValueList.smallCaseForValue = true,
1540 .tagValueList.wrapping = detailsContext.wrapping};
1541 uint8_t nbElementsInPage;
1542
1543 // if first page or forward
1544 if (detailsContext.currentPage <= pageIdx) {
1545 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1546 }
1547 else {
1548 // backward direction
1549 modalContextGetPageInfo(pageIdx + 1, &nbElementsInPage);
1550 detailsContext.currentPairIdx -= nbElementsInPage;
1551 modalContextGetPageInfo(pageIdx, &nbElementsInPage);
1552 detailsContext.currentPairIdx -= nbElementsInPage;
1553 }
1554 detailsContext.currentPage = pageIdx;
1555
1556 content.tagValueList.pairs
1557 = &genericContext.currentTagValues->pairs[detailsContext.currentPairIdx];
1558 content.tagValueList.nbPairs = nbElementsInPage;
1559 detailsContext.currentPairIdx += nbElementsInPage;
1560 if (info.nbPages == 1) {
1561 // if only one page, no navigation bar, and use a footer instead
1562 info.navWithButtons.quitText = "Close";
1563 }
1564
1565 if (modalPageContext != NULL) {
1566 nbgl_pageRelease(modalPageContext);
1567 }
1568 modalPageContext = nbgl_pageDrawGenericContentExt(&pageModalCallback, &info, &content, true);
1569
1570 if (forceFullRefresh) {
1572 }
1573 else {
1575 }
1576}
1577
1578#ifdef NBGL_QRCODE
1579static void displayAddressQRCode(void)
1580{
1581 // display the address as QR Code
1582 nbgl_layoutDescription_t layoutDescription = {.modal = true,
1583 .withLeftBorder = true,
1584 .onActionCallback = &modalLayoutTouchCallback,
1585 .tapActionText = NULL};
1586 nbgl_layoutHeader_t headerDesc = {
1587 .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
1588 nbgl_layoutQRCode_t qrCode = {.url = addressConfirmationContext.tagValuePairs[0].value,
1589 .text1 = NULL,
1590 .centered = true,
1591 .offsetY = 0};
1592
1593 addressConfirmationContext.modalLayout = nbgl_layoutGet(&layoutDescription);
1594 // add empty header for better look
1595 nbgl_layoutAddHeader(addressConfirmationContext.modalLayout, &headerDesc);
1596 // compute nb lines to check whether it shall be shorten (max is 3 lines)
1597 uint16_t nbLines = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT,
1598 addressConfirmationContext.tagValuePairs[0].value,
1600 false);
1601
1602 if (nbLines <= QRCODE_NB_MAX_LINES) {
1603 qrCode.text2 = addressConfirmationContext.tagValuePairs[0].value; // in gray
1604 }
1605 else {
1606 // only keep beginning and end of text, and add ... in the middle
1607 nbgl_textReduceOnNbLines(SMALL_REGULAR_FONT,
1608 addressConfirmationContext.tagValuePairs[0].value,
1610 QRCODE_NB_MAX_LINES,
1611 reducedAddress,
1612 QRCODE_REDUCED_ADDR_LEN);
1613 qrCode.text2 = reducedAddress; // in gray
1614 }
1615
1616 nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
1617
1619 addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
1620 nbgl_layoutDraw(addressConfirmationContext.modalLayout);
1621 nbgl_refresh();
1622}
1623
1624#endif // NBGL_QRCODE
1625
1626// called when header is touched on modal page, to dismiss it
1627static void modalLayoutTouchCallback(int token, uint8_t index)
1628{
1629 UNUSED(index);
1630 if (token == DISMISS_QR_TOKEN) {
1631 // dismiss modal
1632 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
1633 addressConfirmationContext.modalLayout = NULL;
1635 }
1636 else if (token == DISMISS_WARNING_TOKEN) {
1637 // dismiss modal
1638 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1639 // if already at first level, simply redraw the background
1640 if (reviewWithWarnCtx.securityReportLevel <= 1) {
1641 reviewWithWarnCtx.modalLayout = NULL;
1643 }
1644 else {
1645 // We are at level 2 of warning modal, so re-display the level 1
1646 reviewWithWarnCtx.securityReportLevel = 1;
1647 // if preset is used
1648 if (reviewWithWarnCtx.warning->predefinedSet) {
1649 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1650 }
1651 else {
1652 // use customized warning
1653 const nbgl_warningDetails_t *details
1654 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1655 : reviewWithWarnCtx.warning->reviewDetails;
1656 displayCustomizedSecurityReport(details);
1657 }
1658 return;
1659 }
1660 }
1661 else if (token == DISMISS_DETAILS_TOKEN) {
1662 // dismiss modal
1663 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1664 // if already at first level, simply redraw the background
1665 if (choiceWithDetailsCtx.level <= 1) {
1666 choiceWithDetailsCtx.modalLayout = NULL;
1668 }
1669 else {
1670 // We are at level 2 of details modal, so re-display the level 1
1671 choiceWithDetailsCtx.level = 1;
1672 choiceWithDetailsCtx.modalLayout
1673 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1674 }
1675 }
1676 else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
1677 if (sharedContext.usage == SHARE_CTX_REVIEW_WITH_WARNING) {
1678 // dismiss modal before creating a new one
1679 nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
1680 reviewWithWarnCtx.securityReportLevel = 2;
1681 // if preset is used
1682 if (reviewWithWarnCtx.warning->predefinedSet) {
1683 displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
1684 }
1685 else {
1686 // use customized warning
1687 const nbgl_warningDetails_t *details
1688 = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
1689 : reviewWithWarnCtx.warning->reviewDetails;
1690 if (details->type == BAR_LIST_WARNING) {
1691 displayCustomizedSecurityReport(
1692 &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
1693 }
1694 }
1695 }
1696 else if (sharedContext.usage == SHARE_CTX_CHOICE_WITH_DETAILS) {
1697 const nbgl_warningDetails_t *details
1698 = &choiceWithDetailsCtx.details->barList.details[token - FIRST_WARN_BAR_TOKEN];
1699 if (details->title != NO_TYPE_WARNING) {
1700 // dismiss modal before creating a new one
1701 nbgl_layoutRelease(choiceWithDetailsCtx.modalLayout);
1702 choiceWithDetailsCtx.level = 2;
1703 choiceWithDetailsCtx.modalLayout
1704 = displayModalDetails(details, DISMISS_DETAILS_TOKEN);
1705 }
1706 }
1707 return;
1708 }
1709 else {
1710 // dismiss modal
1711 nbgl_layoutRelease(genericContext.modalLayout);
1712 genericContext.modalLayout = NULL;
1714 }
1715 nbgl_refresh();
1716}
1717
1718// called when item are touched in layout
1719static void layoutTouchCallback(int token, uint8_t index)
1720{
1721 // choice buttons in initial warning page
1722 if (token == WARNING_CHOICE_TOKEN) {
1723 if (index == 0) { // top button to exit
1724 reviewWithWarnCtx.choiceCallback(false);
1725 }
1726 else { // bottom button to continue to review
1727 reviewWithWarnCtx.isIntro = false;
1728 if (reviewWithWarnCtx.isStreaming) {
1729 useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
1730 reviewWithWarnCtx.icon,
1731 reviewWithWarnCtx.reviewTitle,
1732 reviewWithWarnCtx.reviewSubTitle,
1733 reviewWithWarnCtx.choiceCallback,
1734 false);
1735 }
1736 else {
1737 useCaseReview(reviewWithWarnCtx.operationType,
1738 reviewWithWarnCtx.tagValueList,
1739 reviewWithWarnCtx.icon,
1740 reviewWithWarnCtx.reviewTitle,
1741 reviewWithWarnCtx.reviewSubTitle,
1742 reviewWithWarnCtx.finishTitle,
1743 &activeTipBox,
1744 reviewWithWarnCtx.choiceCallback,
1745 false,
1746 false);
1747 }
1748 }
1749 }
1750 // top-right button in initial warning page
1751 else if (token == WARNING_BUTTON_TOKEN) {
1752 // start directly at first level of security report
1753 reviewWithWarnCtx.securityReportLevel = 1;
1754 // if preset is used
1755 if (reviewWithWarnCtx.warning->predefinedSet) {
1756 displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
1757 }
1758 else {
1759 // use customized warning
1760 const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
1761 ? reviewWithWarnCtx.warning->introDetails
1762 : reviewWithWarnCtx.warning->reviewDetails;
1763 displayCustomizedSecurityReport(details);
1764 }
1765 }
1766 // top-right button in choice with details page
1767 else if (token == CHOICE_DETAILS_TOKEN) {
1768 choiceWithDetailsCtx.level = 1;
1769 choiceWithDetailsCtx.modalLayout
1770 = displayModalDetails(choiceWithDetailsCtx.details, DISMISS_DETAILS_TOKEN);
1771 }
1772}
1773
1774// called when skip button is touched in footer, during forward only review
1775static void displaySkipWarning(void)
1776{
1778 .cancelText = "Go back to review",
1779 .centeredInfo.text1 = "Skip review?",
1780 .centeredInfo.text2
1781 = "If you're sure you don't need to review all fields, you can skip straight to signing.",
1782 .centeredInfo.text3 = NULL,
1783 .centeredInfo.style = LARGE_CASE_INFO,
1784 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
1785 .centeredInfo.offsetY = 0,
1786 .confirmationText = "Yes, skip",
1787 .confirmationToken = SKIP_TOKEN,
1788 .tuneId = TUNE_TAP_CASUAL,
1789 .modal = true};
1790 if (modalPageContext != NULL) {
1791 nbgl_pageRelease(modalPageContext);
1792 }
1793 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
1795}
1796
1797#ifdef NBGL_KEYPAD
1798// called to update the keypad and automatically show / hide:
1799// - backspace key if no digits are present
1800// - validation key if the min digit is reached
1801// - keypad if the max number of digit is reached
1802static void updateKeyPad(bool add)
1803{
1804 bool enableValidate, enableBackspace, enableDigits;
1805 bool redrawKeypad = false;
1807
1808 enableDigits = (keypadContext.pinLen < keypadContext.pinMaxDigits);
1809 enableValidate = (keypadContext.pinLen >= keypadContext.pinMinDigits);
1810 enableBackspace = (keypadContext.pinLen > 0);
1811 if (add) {
1812 if ((keypadContext.pinLen == keypadContext.pinMinDigits)
1813 || // activate "validate" button on keypad
1814 (keypadContext.pinLen == keypadContext.pinMaxDigits)
1815 || // deactivate "digits" on keypad
1816 (keypadContext.pinLen == 1)) { // activate "backspace"
1817 redrawKeypad = true;
1818 }
1819 }
1820 else { // remove
1821 if ((keypadContext.pinLen == 0) || // deactivate "backspace" button on keypad
1822 (keypadContext.pinLen == (keypadContext.pinMinDigits - 1))
1823 || // deactivate "validate" button on keypad
1824 (keypadContext.pinLen
1825 == (keypadContext.pinMaxDigits - 1))) { // reactivate "digits" on keypad
1826 redrawKeypad = true;
1827 }
1828 }
1829 if (keypadContext.hidden == true) {
1830 nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL);
1831 }
1832 else {
1834 keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry);
1835 }
1836 if (redrawKeypad) {
1838 keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits);
1839 }
1840
1841 if ((!add) && (keypadContext.pinLen == 0)) {
1842 // Full refresh to fully clean the bullets when reaching 0 digits
1843 mode = FULL_COLOR_REFRESH;
1844 }
1846}
1847
1848// called when a key is touched on the keypad
1849static void keypadCallback(char touchedKey)
1850{
1851 switch (touchedKey) {
1852 case BACKSPACE_KEY:
1853 if (keypadContext.pinLen > 0) {
1854 keypadContext.pinLen--;
1855 keypadContext.pinEntry[keypadContext.pinLen] = 0;
1856 }
1857 updateKeyPad(false);
1858 break;
1859
1860 case VALIDATE_KEY:
1861 // Gray out keyboard / buttons as a first user feedback
1862 nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true);
1865
1866 onValidatePin(keypadContext.pinEntry, keypadContext.pinLen);
1867 break;
1868
1869 default:
1870 if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
1871 if (keypadContext.pinLen < keypadContext.pinMaxDigits) {
1872 keypadContext.pinEntry[keypadContext.pinLen] = touchedKey;
1873 keypadContext.pinLen++;
1874 }
1875 updateKeyPad(true);
1876 }
1877 break;
1878 }
1879}
1880
1881static void keypadGeneric_cb(int token, uint8_t index)
1882{
1883 UNUSED(index);
1884 if (token != BACK_TOKEN) {
1885 return;
1886 }
1887 onQuit();
1888}
1889#endif
1890
1905static uint8_t getNbTagValuesInPage(uint8_t nbPairs,
1906 const nbgl_contentTagValueList_t *tagValueList,
1907 uint8_t startIndex,
1908 bool isSkippable,
1909 bool hasConfirmationButton,
1910 bool hasDetailsButton,
1911 bool *requireSpecificDisplay)
1912{
1913 uint8_t nbPairsInPage = 0;
1914 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
1915 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
1916
1917 // if the review is skippable, it means that there is less height for tag/value pairs
1918 // the small centering header becomes a touchable header
1919 if (isSkippable) {
1920 maxUsableHeight -= TOUCHABLE_HEADER_BAR_HEIGHT - SMALL_CENTERING_HEADER;
1921 }
1922
1923 *requireSpecificDisplay = false;
1924 while (nbPairsInPage < nbPairs) {
1925 const nbgl_layoutTagValue_t *pair;
1926 nbgl_font_id_e value_font;
1927 uint16_t nbLines;
1928
1929 // margin between pairs
1930 // 12 or 24 px between each tag/value pair
1931 if (nbPairsInPage > 0) {
1932 currentHeight += INTER_TAG_VALUE_MARGIN;
1933 }
1934 // fetch tag/value pair strings.
1935 if (tagValueList->pairs != NULL) {
1936 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
1937 }
1938 else {
1939 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
1940 }
1941
1942 if (pair->forcePageStart && nbPairsInPage > 0) {
1943 // This pair must be at the top of a page
1944 break;
1945 }
1946
1947 if (pair->centeredInfo) {
1948 if (nbPairsInPage > 0) {
1949 // This pair must be at the top of a page
1950 break;
1951 }
1952 else {
1953 // This pair is the only one of the page and has a specific display behavior
1954 nbPairsInPage = 1;
1955 *requireSpecificDisplay = true;
1956 break;
1957 }
1958 }
1959
1960 // tag height
1961 currentHeight += nbgl_getTextHeightInWidth(
1962 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
1963 // space between tag and value
1964 currentHeight += 4;
1965 // set value font
1966 if (tagValueList->smallCaseForValue) {
1967 value_font = SMALL_REGULAR_FONT;
1968 }
1969 else {
1970 value_font = LARGE_MEDIUM_FONT;
1971 }
1972 // value height
1973 currentHeight += nbgl_getTextHeightInWidth(
1974 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1975 // nb lines for value
1977 value_font, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
1978 if ((currentHeight >= maxUsableHeight) || (nbLines > NB_MAX_LINES_IN_REVIEW)) {
1979 if (nbPairsInPage == 0) {
1980 // Pair too long to fit in a single screen
1981 // It will be the only one of the page and has a specific display behavior
1982 nbPairsInPage = 1;
1983 *requireSpecificDisplay = true;
1984 }
1985 break;
1986 }
1987 nbPairsInPage++;
1988 }
1989 // if this is a TAG_VALUE_CONFIRM and we have reached the last pairs,
1990 // let's check if it still fits with a CONFIRMATION button, and if not,
1991 // remove the last pair
1992 if (hasConfirmationButton && (nbPairsInPage == nbPairs)) {
1993 maxUsableHeight -= UP_FOOTER_BUTTON_HEIGHT;
1994 if (currentHeight > maxUsableHeight) {
1995 nbPairsInPage--;
1996 }
1997 }
1998 // do the same with just a details button
1999 else if (hasDetailsButton) {
2000 maxUsableHeight -= (SMALL_BUTTON_RADIUS * 2);
2001 if (currentHeight > maxUsableHeight) {
2002 nbPairsInPage--;
2003 }
2004 }
2005 return nbPairsInPage;
2006}
2007
2017static uint8_t getNbTagValuesInDetailsPage(uint8_t nbPairs,
2018 const nbgl_contentTagValueList_t *tagValueList,
2019 uint8_t startIndex)
2020{
2021 uint8_t nbPairsInPage = 0;
2022 uint16_t currentHeight = PRE_TAG_VALUE_MARGIN; // upper margin
2023 uint16_t maxUsableHeight = TAG_VALUE_AREA_HEIGHT;
2024
2025 while (nbPairsInPage < nbPairs) {
2026 const nbgl_layoutTagValue_t *pair;
2027
2028 // margin between pairs
2029 // 12 or 24 px between each tag/value pair
2030 if (nbPairsInPage > 0) {
2031 currentHeight += INTER_TAG_VALUE_MARGIN;
2032 }
2033 // fetch tag/value pair strings.
2034 if (tagValueList->pairs != NULL) {
2035 pair = PIC(&tagValueList->pairs[startIndex + nbPairsInPage]);
2036 }
2037 else {
2038 pair = PIC(tagValueList->callback(startIndex + nbPairsInPage));
2039 }
2040
2041 // tag height
2042 currentHeight += nbgl_getTextHeightInWidth(
2043 SMALL_REGULAR_FONT, pair->item, AVAILABLE_WIDTH, tagValueList->wrapping);
2044 // space between tag and value
2045 currentHeight += 4;
2046
2047 // value height
2048 currentHeight += nbgl_getTextHeightInWidth(
2049 SMALL_REGULAR_FONT, pair->value, AVAILABLE_WIDTH, tagValueList->wrapping);
2050
2051 // we have reached the maximum height, it means than there are to many pairs
2052 if (currentHeight >= maxUsableHeight) {
2053 break;
2054 }
2055 nbPairsInPage++;
2056 }
2057 return nbPairsInPage;
2058}
2059
2060static uint8_t getNbPagesForContent(const nbgl_content_t *content,
2061 uint8_t pageIdxStart,
2062 bool isLast,
2063 bool isSkippable)
2064{
2065 uint8_t nbElements = 0;
2066 uint8_t nbPages = 0;
2067 uint8_t nbElementsInPage;
2068 uint8_t elemIdx = 0;
2069 bool flag;
2070
2071 nbElements = getContentNbElement(content);
2072
2073 while (nbElements > 0) {
2074 flag = 0;
2075 // if the current page is not the first one (or last), a navigation bar exists
2076 bool hasNav = !isLast || (pageIdxStart > 0) || (elemIdx > 0);
2077 if (content->type == TAG_VALUE_LIST) {
2078 nbElementsInPage = getNbTagValuesInPage(nbElements,
2079 &content->content.tagValueList,
2080 elemIdx,
2081 isSkippable,
2082 false,
2083 false,
2084 &flag);
2085 }
2086 else if (content->type == TAG_VALUE_CONFIRM) {
2087 nbElementsInPage = getNbTagValuesInPage(nbElements,
2089 elemIdx,
2090 isSkippable,
2091 isLast,
2092 !isLast,
2093 &flag);
2094 }
2095 else if (content->type == INFOS_LIST) {
2096 nbElementsInPage = nbgl_useCaseGetNbInfosInPage(
2097 nbElements, &content->content.infosList, elemIdx, hasNav);
2098 }
2099 else if (content->type == SWITCHES_LIST) {
2100 nbElementsInPage = nbgl_useCaseGetNbSwitchesInPage(
2101 nbElements, &content->content.switchesList, elemIdx, hasNav);
2102 }
2103 else if (content->type == BARS_LIST) {
2104 nbElementsInPage = nbgl_useCaseGetNbBarsInPage(
2105 nbElements, &content->content.barsList, elemIdx, hasNav);
2106 }
2107 else if (content->type == CHOICES_LIST) {
2108 nbElementsInPage = nbgl_useCaseGetNbChoicesInPage(
2109 nbElements, &content->content.choicesList, elemIdx, hasNav);
2110 }
2111 else {
2112 nbElementsInPage = MIN(nbMaxElementsPerContentType[content->type], nbElements);
2113 }
2114
2115 elemIdx += nbElementsInPage;
2116 genericContextSetPageInfo(pageIdxStart + nbPages, nbElementsInPage, flag);
2117 nbElements -= nbElementsInPage;
2118 nbPages++;
2119 }
2120
2121 return nbPages;
2122}
2123
2124static uint8_t getNbPagesForGenericContents(const nbgl_genericContents_t *genericContents,
2125 uint8_t pageIdxStart,
2126 bool isSkippable)
2127{
2128 uint8_t nbPages = 0;
2129 nbgl_content_t content;
2130 const nbgl_content_t *p_content;
2131
2132 for (int i = 0; i < genericContents->nbContents; i++) {
2133 p_content = getContentAtIdx(genericContents, i, &content);
2134 if (p_content == NULL) {
2135 return 0;
2136 }
2137 nbPages += getNbPagesForContent(p_content,
2138 pageIdxStart + nbPages,
2139 (i == (genericContents->nbContents - 1)),
2140 isSkippable);
2141 }
2142
2143 return nbPages;
2144}
2145
2146static void prepareAddressConfirmationPages(const char *address,
2147 const nbgl_contentTagValueList_t *tagValueList,
2148 nbgl_content_t *firstPageContent,
2149 nbgl_content_t *secondPageContent)
2150{
2151 nbgl_contentTagValueConfirm_t *tagValueConfirm;
2152
2153 addressConfirmationContext.tagValuePairs[0].item = "Address";
2154 addressConfirmationContext.tagValuePairs[0].value = address;
2155 addressConfirmationContext.nbPairs = 1;
2156
2157 // First page
2158 firstPageContent->type = TAG_VALUE_CONFIRM;
2159 tagValueConfirm = &firstPageContent->content.tagValueConfirm;
2160
2161#ifdef NBGL_QRCODE
2162 tagValueConfirm->detailsButtonIcon = &QRCODE_ICON;
2163 // only use "Show as QR" when address & pairs are not fitting in a single page
2164 if ((tagValueList != NULL) && (tagValueList->nbPairs < ADDR_VERIF_NB_PAIRS)) {
2166 bool flag;
2167 // copy in intermediate structure
2168 for (uint8_t i = 0; i < tagValueList->nbPairs; i++) {
2169 memcpy(&addressConfirmationContext.tagValuePairs[1 + i],
2170 &tagValueList->pairs[i],
2171 sizeof(nbgl_contentTagValue_t));
2172 addressConfirmationContext.nbPairs++;
2173 }
2174 // check how many can fit in a page
2175 memcpy(&tmpList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2176 tmpList.nbPairs = addressConfirmationContext.nbPairs;
2177 tmpList.pairs = addressConfirmationContext.tagValuePairs;
2178 addressConfirmationContext.nbPairs = getNbTagValuesInPage(
2179 addressConfirmationContext.nbPairs, &tmpList, 0, false, true, true, &flag);
2180 // if they don't all fit, keep only the address
2181 if (tmpList.nbPairs > addressConfirmationContext.nbPairs) {
2182 addressConfirmationContext.nbPairs = 1;
2183 }
2184 }
2185 else {
2186 tagValueConfirm->detailsButtonText = NULL;
2187 }
2188 tagValueConfirm->detailsButtonToken = ADDRESS_QRCODE_BUTTON_TOKEN;
2189#else // NBGL_QRCODE
2190 tagValueConfirm->detailsButtonText = NULL;
2191 tagValueConfirm->detailsButtonIcon = NULL;
2192#endif // NBGL_QRCODE
2193 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2194 tagValueConfirm->tagValueList.nbPairs = addressConfirmationContext.nbPairs;
2195 tagValueConfirm->tagValueList.pairs = addressConfirmationContext.tagValuePairs;
2196 tagValueConfirm->tagValueList.smallCaseForValue = false;
2197 tagValueConfirm->tagValueList.nbMaxLinesForValue = 0;
2198 tagValueConfirm->tagValueList.wrapping = false;
2199 // if it's an extended address verif, it takes 2 pages, so display a "Tap to continue", and
2200 // no confirmation button
2201 if ((tagValueList != NULL)
2202 && (tagValueList->nbPairs > (addressConfirmationContext.nbPairs - 1))) {
2203 tagValueConfirm->detailsButtonText = "Show as QR";
2204 tagValueConfirm->confirmationText = NULL;
2205 // the second page is dedicated to the extended tag/value pairs
2206 secondPageContent->type = TAG_VALUE_CONFIRM;
2207 tagValueConfirm = &secondPageContent->content.tagValueConfirm;
2208 tagValueConfirm->confirmationText = "Confirm";
2209 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2210 tagValueConfirm->detailsButtonText = NULL;
2211 tagValueConfirm->detailsButtonIcon = NULL;
2212 tagValueConfirm->tuneId = TUNE_TAP_CASUAL;
2213 memcpy(&tagValueConfirm->tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
2214 tagValueConfirm->tagValueList.nbPairs
2215 = tagValueList->nbPairs - (addressConfirmationContext.nbPairs - 1);
2216 tagValueConfirm->tagValueList.pairs
2217 = &tagValueList->pairs[addressConfirmationContext.nbPairs - 1];
2218 }
2219 else {
2220 // otherwise no tap to continue but a confirmation button
2221 tagValueConfirm->confirmationText = "Confirm";
2222 tagValueConfirm->confirmationToken = CONFIRM_TOKEN;
2223 }
2224}
2225
2226static void bundleNavStartHome(void)
2227{
2228 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2229
2230 useCaseHomeExt(context->appName,
2231 context->appIcon,
2232 context->tagline,
2233 context->settingContents != NULL ? true : false,
2234 &context->homeAction,
2235 bundleNavStartSettings,
2236 context->quitCallback);
2237}
2238
2239static void bundleNavStartSettingsAtPage(uint8_t initSettingPage)
2240{
2241 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
2242
2243 nbgl_useCaseGenericSettings(context->appName,
2244 initSettingPage,
2245 context->settingContents,
2246 context->infosList,
2247 bundleNavStartHome);
2248}
2249
2250static void bundleNavStartSettings(void)
2251{
2252 bundleNavStartSettingsAtPage(0);
2253}
2254
2255static void bundleNavReviewConfirmRejection(void)
2256{
2257 bundleNavContext.review.choiceCallback(false);
2258}
2259
2260static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operationType,
2261 nbgl_callback_t callback)
2262{
2263 const char *title;
2264 const char *confirmText;
2265 // clear skip and blind bits
2266 operationType
2267 &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION);
2268 if (operationType == TYPE_TRANSACTION) {
2269 title = "Reject transaction?";
2270 confirmText = "Go back to transaction";
2271 }
2272 else if (operationType == TYPE_MESSAGE) {
2273 title = "Reject message?";
2274 confirmText = "Go back to message";
2275 }
2276 else {
2277 title = "Reject operation?";
2278 confirmText = "Go back to operation";
2279 }
2280
2281 // display a choice to confirm/cancel rejection
2282 nbgl_useCaseConfirm(title, NULL, "Yes, reject", confirmText, callback);
2283}
2284
2285static void bundleNavReviewChoice(bool confirm)
2286{
2287 if (confirm) {
2288 bundleNavContext.review.choiceCallback(true);
2289 }
2290 else {
2291 bundleNavReviewAskRejectionConfirmation(bundleNavContext.review.operationType,
2292 bundleNavReviewConfirmRejection);
2293 }
2294}
2295
2296static void bundleNavReviewStreamingConfirmRejection(void)
2297{
2298 bundleNavContext.reviewStreaming.choiceCallback(false);
2299}
2300
2301static void bundleNavReviewStreamingChoice(bool confirm)
2302{
2303 if (confirm) {
2304 // Display a spinner if it wasn't the finish step
2305 if (STARTING_CONTENT.type != INFO_LONG_PRESS) {
2306 nbgl_useCaseSpinner("Processing");
2307 }
2308 bundleNavContext.reviewStreaming.choiceCallback(true);
2309 }
2310 else {
2311 bundleNavReviewAskRejectionConfirmation(bundleNavContext.reviewStreaming.operationType,
2312 bundleNavReviewStreamingConfirmRejection);
2313 }
2314}
2315
2316// function used to display the details in modal (from Choice with details or from Customized
2317// security report) it can be either a single page with text or QR code, or a list of touchable bars
2318// leading to single pages
2319static nbgl_layout_t *displayModalDetails(const nbgl_warningDetails_t *details, uint8_t token)
2320{
2321 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2322 .withLeftBorder = true,
2323 .onActionCallback = modalLayoutTouchCallback,
2324 .tapActionText = NULL};
2326 .separationLine = true,
2327 .backAndText.icon = NULL,
2328 .backAndText.tuneId = TUNE_TAP_CASUAL,
2329 .backAndText.token = token};
2330 uint8_t i;
2331 nbgl_layout_t *layout;
2332
2333 layout = nbgl_layoutGet(&layoutDescription);
2334 headerDesc.backAndText.text = details->title;
2335 nbgl_layoutAddHeader(layout, &headerDesc);
2336 if (details->type == BAR_LIST_WARNING) {
2337 // if more than one warning, so use a list of touchable bars
2338 for (i = 0; i < details->barList.nbBars; i++) {
2339 nbgl_layoutBar_t bar;
2340 bar.text = details->barList.texts[i];
2341 bar.subText = details->barList.subTexts[i];
2342 bar.iconRight
2343 = (details->barList.details[i].type != NO_TYPE_WARNING) ? &PUSH_ICON : NULL;
2344 bar.iconLeft = details->barList.icons[i];
2345 bar.token = FIRST_WARN_BAR_TOKEN + i;
2346 bar.tuneId = TUNE_TAP_CASUAL;
2347 bar.large = false;
2348 bar.inactive = false;
2349 nbgl_layoutAddTouchableBar(layout, &bar);
2351 }
2352 }
2353 else if (details->type == QRCODE_WARNING) {
2354#ifdef NBGL_QRCODE
2355 // display a QR Code
2356 nbgl_layoutAddQRCode(layout, &details->qrCode);
2357#endif // NBGL_QRCODE
2358 headerDesc.backAndText.text = details->title;
2359 }
2360 else if (details->type == CENTERED_INFO_WARNING) {
2361 // display a centered info
2362 nbgl_layoutAddContentCenter(layout, &details->centeredInfo);
2363 headerDesc.separationLine = false;
2364 }
2365 nbgl_layoutDraw(layout);
2366 nbgl_refresh();
2367 return layout;
2368}
2369
2370// function used to display the security level page in modal
2371// it can be either a single page with text or QR code, or a list of touchable bars leading
2372// to single pages
2373static void displaySecurityReport(uint32_t set)
2374{
2375 nbgl_layoutDescription_t layoutDescription = {.modal = true,
2376 .withLeftBorder = true,
2377 .onActionCallback = modalLayoutTouchCallback,
2378 .tapActionText = NULL};
2380 .separationLine = true,
2381 .backAndText.icon = NULL,
2382 .backAndText.tuneId = TUNE_TAP_CASUAL,
2383 .backAndText.token = DISMISS_WARNING_TOKEN};
2384 nbgl_layoutFooter_t footerDesc
2385 = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
2386 uint8_t i;
2387 uint8_t nbWarnings = 0;
2388 const char *provider;
2389
2390 reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
2391
2392 // count the number of warnings
2393 for (i = 0; i < NB_WARNING_TYPES; i++) {
2394 if ((set & (1 << i)) && (i != W3C_NO_THREAT_WARN)) {
2395 nbWarnings++;
2396 }
2397 }
2398
2399 // display a list of touchable bars in some conditions:
2400 // - we must be in the first level of security report
2401 // - and be in the review itself (not from the intro/warning screen)
2402 //
2403 if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) {
2404 for (i = 0; i < NB_WARNING_TYPES; i++) {
2405 if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) {
2406 nbgl_layoutBar_t bar = {0};
2407 if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN)
2408 || (reviewWithWarnCtx.warning->providerMessage == NULL)) {
2409 bar.subText = securityReportItems[i].subText;
2410 }
2411 else {
2412 bar.subText = reviewWithWarnCtx.warning->providerMessage;
2413 }
2414 bar.text = securityReportItems[i].text;
2415 if (i != W3C_NO_THREAT_WARN) {
2416 bar.iconRight = &PUSH_ICON;
2417 bar.token = FIRST_WARN_BAR_TOKEN + i;
2418 bar.tuneId = TUNE_TAP_CASUAL;
2419 }
2420 else {
2422 }
2423 bar.iconLeft = securityReportItems[i].icon;
2424 nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
2425 nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
2426 }
2427 }
2428 headerDesc.backAndText.text = "Security report";
2429 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2430 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2431 nbgl_refresh();
2432 return;
2433 }
2434 if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
2435 provider = reviewWithWarnCtx.warning->reportProvider;
2436 }
2437 else {
2438 provider = "[unknown]";
2439 }
2440 if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) {
2441 size_t urlLen = 0;
2442 char *destStr
2443 = tmpString
2444 + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings
2445#ifdef NBGL_QRCODE
2446 // display a QR Code
2447 nbgl_layoutQRCode_t qrCode = {.url = destStr,
2448 .text1 = reviewWithWarnCtx.warning->reportUrl,
2449 .text2 = "Scan to view full report",
2450 .centered = true,
2451 .offsetY = 0};
2452 // add "https://"" as prefix of the given URL
2453 snprintf(destStr,
2454 W3C_DESCRIPTION_MAX_LEN / 2,
2455 "https://%s",
2456 reviewWithWarnCtx.warning->reportUrl);
2457 urlLen = strlen(destStr) + 1;
2458 nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
2459 footerDesc.emptySpace.height = 24;
2460#endif // NBGL_QRCODE
2461 // use the next part of destStr for back bar text
2462 snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider);
2463 headerDesc.backAndText.text = destStr + urlLen;
2464 }
2465 else if (set & (1 << BLIND_SIGNING_WARN)) {
2466 // display a centered if in review
2467 nbgl_contentCenter_t info = {0};
2468 info.icon = &LARGE_WARNING_ICON;
2469 info.title = "This transaction cannot be Clear Signed";
2470 info.description
2471 = "This transaction or message cannot be decoded fully. If you choose to sign, you "
2472 "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: "
2473 "ledger.com/e8";
2474 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2475 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2476 headerDesc.separationLine = false;
2477 }
2478 else if (set & (1 << W3C_ISSUE_WARN)) {
2479 // if W3 Checks issue, display a centered info
2480 nbgl_contentCenter_t info = {0};
2481 info.icon = &LARGE_WARNING_ICON;
2482 info.title = "Transaction Check unavailable";
2483 info.description
2484 = "If you're not using the Ledger Wallet app, Transaction Check might not work. If "
2485 "you "
2486 "are using Ledger Wallet, reject the transaction and try again.\n\nGet help at "
2487 "ledger.com/e11";
2488 nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
2489 footerDesc.emptySpace.height = SMALL_CENTERING_HEADER;
2490 headerDesc.separationLine = false;
2491 }
2492 nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
2493 if (footerDesc.emptySpace.height > 0) {
2494 nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
2495 }
2496 nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
2497 nbgl_refresh();
2498}
2499
2500// function used to display the security level page in modal
2501// it can be either a single page with text or QR code, or a list of touchable bars leading
2502// to single pages
2503static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
2504{
2505 reviewWithWarnCtx.modalLayout = displayModalDetails(details, DISMISS_WARNING_TOKEN);
2506}
2507
2508// function used to display the initial warning page when starting a "review with warning"
2509static void displayInitialWarning(void)
2510{
2511 // Play notification sound
2512#ifdef HAVE_PIEZO_SOUND
2513 tune_index_e tune = TUNE_RESERVED;
2514#endif // HAVE_PIEZO_SOUND
2515 nbgl_layoutDescription_t layoutDescription;
2516 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
2517 .token = WARNING_CHOICE_TOKEN,
2518 .topText = "Back to safety",
2519 .style = ROUNDED_AND_FOOTER_STYLE,
2520 .tuneId = TUNE_TAP_CASUAL};
2521 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
2522 .separationLine = false,
2523 .emptySpace.height = MEDIUM_CENTERING_HEADER};
2524 uint32_t set = reviewWithWarnCtx.warning->predefinedSet
2525 & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN));
2526
2527 reviewWithWarnCtx.isIntro = true;
2528
2529 layoutDescription.modal = false;
2530 layoutDescription.withLeftBorder = true;
2531
2532 layoutDescription.onActionCallback = layoutTouchCallback;
2533 layoutDescription.tapActionText = NULL;
2534
2535 layoutDescription.ticker.tickerCallback = NULL;
2536 reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
2537
2538 nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
2539 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2541 reviewWithWarnCtx.layoutCtx,
2542 (set == (1 << BLIND_SIGNING_WARN)) ? &INFO_I_ICON : &QRCODE_ICON,
2543 WARNING_BUTTON_TOKEN,
2544 TUNE_TAP_CASUAL);
2545 }
2546 else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
2547 nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
2548 reviewWithWarnCtx.warning->introTopRightIcon,
2549 WARNING_BUTTON_TOKEN,
2550 TUNE_TAP_CASUAL);
2551 }
2552 // add main content
2553 // if predefined content is configured, use it preferably
2554 if (reviewWithWarnCtx.warning->predefinedSet != 0) {
2555 nbgl_contentCenter_t info = {0};
2556
2557 const char *provider;
2558
2559 // default icon
2560 info.icon = &LARGE_WARNING_ICON;
2561
2562 // use small title only if not Blind signing only
2563 if (set != (1 << BLIND_SIGNING_WARN)) {
2564 if (reviewWithWarnCtx.warning->reportProvider) {
2565 provider = reviewWithWarnCtx.warning->reportProvider;
2566 }
2567 else {
2568 provider = "[unknown]";
2569 }
2570 info.smallTitle = tmpString;
2571 snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider);
2572 }
2573
2574 if (set == (1 << BLIND_SIGNING_WARN)) {
2575 info.title = "Blind signing ahead";
2576 info.description
2577 = "This transaction's details are not fully verifiable. If you sign, you could "
2578 "lose all your assets.";
2579 buttonsInfo.bottomText = "Continue anyway";
2580#ifdef HAVE_PIEZO_SOUND
2581 tune = TUNE_NEUTRAL;
2582#endif // HAVE_PIEZO_SOUND
2583 }
2584 else if (set & (1 << W3C_RISK_DETECTED_WARN)) {
2585 info.title = "Potential risk";
2586 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2587 info.description = "Unidentified risk";
2588 }
2589 else {
2590 info.description = reviewWithWarnCtx.warning->providerMessage;
2591 }
2592 buttonsInfo.bottomText = "Accept risk and continue";
2593#ifdef HAVE_PIEZO_SOUND
2594 tune = TUNE_NEUTRAL;
2595#endif // HAVE_PIEZO_SOUND
2596 }
2597 else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
2598 info.title = "Critical threat";
2599 if (reviewWithWarnCtx.warning->providerMessage == NULL) {
2600 info.description = "Unidentified threat";
2601 }
2602 else {
2603 info.description = reviewWithWarnCtx.warning->providerMessage;
2604 }
2605 buttonsInfo.bottomText = "Accept threat and continue";
2606#ifdef HAVE_PIEZO_SOUND
2607 tune = TUNE_ERROR;
2608#endif // HAVE_PIEZO_SOUND
2609 }
2610 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
2611 }
2612 else if (reviewWithWarnCtx.warning->info != NULL) {
2613 // if no predefined content, use custom one
2614 nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
2615#ifdef HAVE_PIEZO_SOUND
2616 tune = TUNE_LOOK_AT_ME;
2617#endif // HAVE_PIEZO_SOUND
2618 }
2619 // add button and footer on bottom
2620 nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
2621
2622#ifdef HAVE_PIEZO_SOUND
2623 if (tune != TUNE_RESERVED) {
2624 os_io_seph_cmd_piezo_play_tune(tune);
2625 }
2626#endif // HAVE_PIEZO_SOUND
2627 nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
2628 nbgl_refresh();
2629}
2630
2631// function to factorize code for reviews tipbox
2632static void initWarningTipBox(const nbgl_tipBox_t *tipBox)
2633{
2634 const char *predefinedTipBoxText = NULL;
2635
2636 // if warning is valid and a warning requires a tip-box
2637 if (reviewWithWarnCtx.warning) {
2638 if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) {
2639 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2640 predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required.";
2641 }
2642 else {
2643 predefinedTipBoxText = "Transaction Check unavailable";
2644 }
2645 }
2646 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) {
2647 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2648 predefinedTipBoxText = "Critical threat detected.\nBlind signing required.";
2649 }
2650 else {
2651 predefinedTipBoxText = "Critical threat detected.";
2652 }
2653 }
2654 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) {
2655 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2656 predefinedTipBoxText = "Potential risk detected.\nBlind signing required.";
2657 }
2658 else {
2659 predefinedTipBoxText = "Potential risk detected.";
2660 }
2661 }
2662 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) {
2663 if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2664 predefinedTipBoxText
2665 = "No threat detected by Transaction Check but blind signing required.";
2666 }
2667 else {
2668 predefinedTipBoxText = "No threat detected by Transaction Check.";
2669 }
2670 }
2671 else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) {
2672 predefinedTipBoxText = "Blind signing required.";
2673 }
2674 }
2675
2676 if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) {
2677 // do not display "Swipe to review" if a tip-box is displayed
2678 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
2679 if (predefinedTipBoxText != NULL) {
2680 genericContext.validWarningCtx = true;
2681 STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL;
2682 STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText;
2683 }
2684 else {
2685 STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
2686 STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
2687 }
2688 STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
2689 STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL;
2690 }
2691}
2692
2693// function to factorize code for all simple reviews
2694static void useCaseReview(nbgl_operationType_t operationType,
2695 const nbgl_contentTagValueList_t *tagValueList,
2696 const nbgl_icon_details_t *icon,
2697 const char *reviewTitle,
2698 const char *reviewSubTitle,
2699 const char *finishTitle,
2700 const nbgl_tipBox_t *tipBox,
2701 nbgl_choiceCallback_t choiceCallback,
2702 bool isLight,
2703 bool playNotifSound)
2704{
2705 bundleNavContext.review.operationType = operationType;
2706 bundleNavContext.review.choiceCallback = choiceCallback;
2707
2708 // memorize context
2709 onChoice = bundleNavReviewChoice;
2710 navType = GENERIC_NAV;
2711 pageTitle = NULL;
2712
2713 genericContext.genericContents.contentsList = localContentsList;
2714 genericContext.genericContents.nbContents = 3;
2715 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
2716
2717 // First a centered info
2718 STARTING_CONTENT.type = EXTENDED_CENTER;
2719 prepareReviewFirstPage(
2720 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2721
2722 // Prepare un tipbox if needed
2723 initWarningTipBox(tipBox);
2724
2725 // Then the tag/value pairs
2726 localContentsList[1].type = TAG_VALUE_LIST;
2727 memcpy(&localContentsList[1].content.tagValueList,
2728 tagValueList,
2730 localContentsList[1].contentActionCallback = tagValueList->actionCallback;
2731
2732 // The last page
2733 if (isLight) {
2734 localContentsList[2].type = INFO_BUTTON;
2735 prepareReviewLightLastPage(
2736 operationType, &localContentsList[2].content.infoButton, icon, finishTitle);
2737 }
2738 else {
2739 localContentsList[2].type = INFO_LONG_PRESS;
2740 prepareReviewLastPage(
2741 operationType, &localContentsList[2].content.infoLongPress, icon, finishTitle);
2742 }
2743
2744 // compute number of pages & fill navigation structure
2745 uint8_t nbPages = getNbPagesForGenericContents(
2746 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2747 prepareNavInfo(true, nbPages, getRejectReviewText(operationType));
2748
2749 // Play notification sound if required
2750 if (playNotifSound) {
2751#ifdef HAVE_PIEZO_SOUND
2752 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2753#endif // HAVE_PIEZO_SOUND
2754 }
2755
2756 displayGenericContextPage(0, true);
2757}
2758
2759// function to factorize code for all streaming reviews
2760static void useCaseReviewStreamingStart(nbgl_operationType_t operationType,
2761 const nbgl_icon_details_t *icon,
2762 const char *reviewTitle,
2763 const char *reviewSubTitle,
2764 nbgl_choiceCallback_t choiceCallback,
2765 bool playNotifSound)
2766{
2767 bundleNavContext.reviewStreaming.operationType = operationType;
2768 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
2769 bundleNavContext.reviewStreaming.icon = icon;
2770
2771 // memorize context
2772 onChoice = bundleNavReviewStreamingChoice;
2773 navType = STREAMING_NAV;
2774 pageTitle = NULL;
2775
2776 genericContext.genericContents.contentsList = localContentsList;
2777 genericContext.genericContents.nbContents = 1;
2778 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
2779
2780 // First a centered info
2781 STARTING_CONTENT.type = EXTENDED_CENTER;
2782 prepareReviewFirstPage(
2783 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
2784
2785 // Prepare un tipbox if needed
2786 initWarningTipBox(NULL);
2787
2788 // compute number of pages & fill navigation structure
2789 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
2790 &genericContext.genericContents, 0, (operationType & SKIPPABLE_OPERATION));
2791 prepareNavInfo(true, NBGL_NO_PROGRESS_INDICATOR, getRejectReviewText(operationType));
2792 // no back button on first page
2793 navInfo.navWithButtons.backButton = false;
2794
2795 // Play notification sound if required
2796 if (playNotifSound) {
2797#ifdef HAVE_PIEZO_SOUND
2798 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
2799#endif // HAVE_PIEZO_SOUND
2800 }
2801
2802 displayGenericContextPage(0, true);
2803}
2804
2821static void useCaseHomeExt(const char *appName,
2822 const nbgl_icon_details_t *appIcon,
2823 const char *tagline,
2824 bool withSettings,
2825 nbgl_homeAction_t *homeAction,
2826 nbgl_callback_t topRightCallback,
2827 nbgl_callback_t quitCallback)
2828{
2829 nbgl_pageInfoDescription_t info = {.centeredInfo.icon = appIcon,
2830 .centeredInfo.text1 = appName,
2831 .centeredInfo.text3 = NULL,
2832 .centeredInfo.style = LARGE_CASE_INFO,
2833 .centeredInfo.offsetY = 0,
2834 .footerText = NULL,
2835 .bottomButtonStyle = QUIT_APP_TEXT,
2836 .tapActionText = NULL,
2837 .topRightStyle = withSettings ? SETTINGS_ICON : INFO_ICON,
2838 .topRightToken = CONTINUE_TOKEN,
2839 .tuneId = TUNE_TAP_CASUAL};
2840 reset_callbacks_and_context();
2841
2842 if ((homeAction->text != NULL) || (homeAction->icon != NULL)) {
2843 // trick to use ACTION_BUTTON_TOKEN for action and quit, with index used to distinguish
2844 info.bottomButtonsToken = ACTION_BUTTON_TOKEN;
2845 onAction = homeAction->callback;
2846 info.actionButtonText = homeAction->text;
2847 info.actionButtonIcon = homeAction->icon;
2850 }
2851 else {
2852 info.bottomButtonsToken = QUIT_TOKEN;
2853 onAction = NULL;
2854 info.actionButtonText = NULL;
2855 info.actionButtonIcon = NULL;
2856 }
2857 if (tagline == NULL) {
2858 if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
2859 snprintf(tmpString,
2861 "This app enables signing\ntransactions on its network.");
2862 }
2863 else {
2864 snprintf(tmpString,
2866 "%s %s\n%s",
2868 appName,
2870 }
2871
2872 // If there is more than 3 lines, it means the appName was split, so we put it on the next
2873 // line
2874 if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
2875 snprintf(tmpString,
2877 "%s\n%s %s",
2879 appName,
2881 }
2882 info.centeredInfo.text2 = tmpString;
2883 }
2884 else {
2885 info.centeredInfo.text2 = tagline;
2886 }
2887
2888 onContinue = topRightCallback;
2889 onQuit = quitCallback;
2890
2891 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
2893}
2894
2903static void displayDetails(const char *tag, const char *value, bool wrapping)
2904{
2905 memset(&detailsContext, 0, sizeof(detailsContext));
2906
2907 uint16_t nbLines
2908 = nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, value, AVAILABLE_WIDTH, wrapping);
2909
2910 // initialize context
2911 detailsContext.tag = tag;
2912 detailsContext.value = value;
2913 detailsContext.nbPages = (nbLines + NB_MAX_LINES_IN_DETAILS - 1) / NB_MAX_LINES_IN_DETAILS;
2914 detailsContext.currentPage = 0;
2915 detailsContext.wrapping = wrapping;
2916 // add some spare for room lost with "..." substitution
2917 if (detailsContext.nbPages > 1) {
2918 uint16_t nbLostChars = (detailsContext.nbPages - 1) * 3;
2919 uint16_t nbLostLines = (nbLostChars + ((AVAILABLE_WIDTH) / 16) - 1)
2920 / ((AVAILABLE_WIDTH) / 16); // 16 for average char width
2921 uint8_t nbLinesInLastPage
2922 = nbLines - ((detailsContext.nbPages - 1) * NB_MAX_LINES_IN_DETAILS);
2923
2924 detailsContext.nbPages += nbLostLines / NB_MAX_LINES_IN_DETAILS;
2925 if ((nbLinesInLastPage + (nbLostLines % NB_MAX_LINES_IN_DETAILS))
2927 detailsContext.nbPages++;
2928 }
2929 }
2930
2931 displayDetailsPage(0, true);
2932}
2933
2934// function used to display the modal containing alias tag-value pairs
2935static void displayTagValueListModal(const nbgl_contentTagValueList_t *tagValues)
2936{
2937 uint8_t nbElements = 0;
2938 uint8_t nbElementsInPage;
2939 uint8_t elemIdx = 0;
2940
2941 // initialize context
2942 memset(&detailsContext, 0, sizeof(detailsContext));
2943 nbElements = tagValues->nbPairs;
2944
2945 while (nbElements > 0) {
2946 nbElementsInPage = getNbTagValuesInDetailsPage(nbElements, tagValues, elemIdx);
2947
2948 elemIdx += nbElementsInPage;
2949 modalContextSetPageInfo(detailsContext.nbPages, nbElementsInPage);
2950 nbElements -= nbElementsInPage;
2951 detailsContext.nbPages++;
2952 }
2953
2954 displayTagValueListModalPage(0, true);
2955}
2956
2957/**********************
2958 * GLOBAL FUNCTIONS
2959 **********************/
2960
2973uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
2974 const nbgl_contentTagValueList_t *tagValueList,
2975 uint8_t startIndex,
2976 bool *requireSpecificDisplay)
2977{
2978 return getNbTagValuesInPage(
2979 nbPairs, tagValueList, startIndex, false, false, false, requireSpecificDisplay);
2980}
2981
2995uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
2996 const nbgl_contentTagValueList_t *tagValueList,
2997 uint8_t startIndex,
2998 bool isSkippable,
2999 bool *requireSpecificDisplay)
3000{
3001 return getNbTagValuesInPage(
3002 nbPairs, tagValueList, startIndex, isSkippable, false, false, requireSpecificDisplay);
3003}
3004
3014uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
3015 const nbgl_contentInfoList_t *infosList,
3016 uint8_t startIndex,
3017 bool withNav)
3018{
3019 uint8_t nbInfosInPage = 0;
3020 uint16_t currentHeight = 0;
3021 uint16_t previousHeight;
3022 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3023 const char *const *infoContents = PIC(infosList->infoContents);
3024
3025 while (nbInfosInPage < nbInfos) {
3026 // The type string must be a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3027 currentHeight
3028 += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING + LIST_ITEM_HEADING_SUB_TEXT;
3029
3030 // content height
3031 currentHeight += nbgl_getTextHeightInWidth(SMALL_REGULAR_FONT,
3032 PIC(infoContents[startIndex + nbInfosInPage]),
3034 true);
3035 // if height is over the limit
3036 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3037 // if there was no nav, now there will be, so it can be necessary to remove the last
3038 // item
3039 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3040 nbInfosInPage--;
3041 }
3042 break;
3043 }
3044 previousHeight = currentHeight;
3045 nbInfosInPage++;
3046 }
3047 return nbInfosInPage;
3048}
3049
3059uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
3060 const nbgl_contentSwitchesList_t *switchesList,
3061 uint8_t startIndex,
3062 bool withNav)
3063{
3064 uint8_t nbSwitchesInPage = 0;
3065 uint16_t currentHeight = 0;
3066 uint16_t previousHeight = 0;
3067 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3068 nbgl_contentSwitch_t *switchArray = (nbgl_contentSwitch_t *) PIC(switchesList->switches);
3069
3070 while (nbSwitchesInPage < nbSwitches) {
3071 nbgl_contentSwitch_t *curSwitch = &switchArray[startIndex + nbSwitchesInPage];
3072 // The text string is either a 1 liner and its height is LIST_ITEM_MIN_TEXT_HEIGHT
3073 // or we use its height directly
3074 uint16_t textHeight = MAX(
3075 LIST_ITEM_MIN_TEXT_HEIGHT,
3076 nbgl_getTextHeightInWidth(SMALL_BOLD_FONT, curSwitch->text, AVAILABLE_WIDTH, true));
3077 currentHeight += textHeight + 2 * LIST_ITEM_PRE_HEADING;
3078
3079 if (curSwitch->subText) {
3080 currentHeight += LIST_ITEM_HEADING_SUB_TEXT;
3081
3082 // sub-text height
3083 currentHeight += nbgl_getTextHeightInWidth(
3084 SMALL_REGULAR_FONT, curSwitch->subText, AVAILABLE_WIDTH, true);
3085 }
3086 // if height is over the limit
3087 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3088 break;
3089 }
3090 previousHeight = currentHeight;
3091 nbSwitchesInPage++;
3092 }
3093 // if there was no nav, now there will be, so it can be necessary to remove the last
3094 // item
3095 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3096 nbSwitchesInPage--;
3097 }
3098 return nbSwitchesInPage;
3099}
3100
3110uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
3111 const nbgl_contentBarsList_t *barsList,
3112 uint8_t startIndex,
3113 bool withNav)
3114{
3115 uint8_t nbBarsInPage = 0;
3116 uint16_t currentHeight = 0;
3117 uint16_t previousHeight;
3118 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3119
3120 UNUSED(barsList);
3121 UNUSED(startIndex);
3122
3123 while (nbBarsInPage < nbBars) {
3124 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3125 // if height is over the limit
3126 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3127 break;
3128 }
3129 previousHeight = currentHeight;
3130 nbBarsInPage++;
3131 }
3132 // if there was no nav, now there may will be, so it can be necessary to remove the last
3133 // item
3134 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3135 nbBarsInPage--;
3136 }
3137 return nbBarsInPage;
3138}
3139
3149uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
3150 const nbgl_contentRadioChoice_t *choicesList,
3151 uint8_t startIndex,
3152 bool withNav)
3153{
3154 uint8_t nbChoicesInPage = 0;
3155 uint16_t currentHeight = 0;
3156 uint16_t previousHeight;
3157 uint16_t navHeight = withNav ? SIMPLE_FOOTER_HEIGHT : 0;
3158
3159 UNUSED(choicesList);
3160 UNUSED(startIndex);
3161
3162 while (nbChoicesInPage < nbChoices) {
3163 currentHeight += LIST_ITEM_MIN_TEXT_HEIGHT + 2 * LIST_ITEM_PRE_HEADING;
3164 // if height is over the limit
3165 if (currentHeight >= (INFOS_AREA_HEIGHT - navHeight)) {
3166 // if there was no nav, now there will be, so it can be necessary to remove the last
3167 // item
3168 if (!withNav && (previousHeight >= (INFOS_AREA_HEIGHT - SIMPLE_FOOTER_HEIGHT))) {
3169 nbChoicesInPage--;
3170 }
3171 break;
3172 }
3173 previousHeight = currentHeight;
3174 nbChoicesInPage++;
3175 }
3176 return nbChoicesInPage;
3177}
3178
3186{
3187 uint8_t nbPages = 0;
3188 uint8_t nbPairs = tagValueList->nbPairs;
3189 uint8_t nbPairsInPage;
3190 uint8_t i = 0;
3191 bool flag;
3192
3193 while (i < tagValueList->nbPairs) {
3194 // upper margin
3195 nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
3196 i += nbPairsInPage;
3197 nbPairs -= nbPairsInPage;
3198 nbPages++;
3199 }
3200 return nbPages;
3201}
3202
3207void nbgl_useCaseHome(const char *appName,
3208 const nbgl_icon_details_t *appIcon,
3209 const char *tagline,
3210 bool withSettings,
3211 nbgl_callback_t topRightCallback,
3212 nbgl_callback_t quitCallback)
3213{
3214 nbgl_homeAction_t homeAction = {0};
3215 useCaseHomeExt(
3216 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3217}
3218
3223void nbgl_useCaseHomeExt(const char *appName,
3224 const nbgl_icon_details_t *appIcon,
3225 const char *tagline,
3226 bool withSettings,
3227 const char *actionButtonText,
3228 nbgl_callback_t actionCallback,
3229 nbgl_callback_t topRightCallback,
3230 nbgl_callback_t quitCallback)
3231{
3232 nbgl_homeAction_t homeAction = {.callback = actionCallback,
3233 .icon = NULL,
3234 .style = STRONG_HOME_ACTION,
3235 .text = actionButtonText};
3236
3237 useCaseHomeExt(
3238 appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
3239}
3240
3254void nbgl_useCaseNavigableContent(const char *title,
3255 uint8_t initPage,
3256 uint8_t nbPages,
3257 nbgl_callback_t quitCallback,
3258 nbgl_navCallback_t navCallback,
3259 nbgl_layoutTouchCallback_t controlsCallback)
3260{
3261 reset_callbacks_and_context();
3262
3263 // memorize context
3264 onQuit = quitCallback;
3265 onNav = navCallback;
3266 onControls = controlsCallback;
3267 pageTitle = title;
3268 navType = SETTINGS_NAV;
3269
3270 // fill navigation structure
3271 prepareNavInfo(false, nbPages, NULL);
3272
3273 displaySettingsPage(initPage, true);
3274}
3275
3281void nbgl_useCaseSettings(const char *title,
3282 uint8_t initPage,
3283 uint8_t nbPages,
3284 bool touchable,
3285 nbgl_callback_t quitCallback,
3286 nbgl_navCallback_t navCallback,
3287 nbgl_layoutTouchCallback_t controlsCallback)
3288{
3289 UNUSED(touchable);
3291 title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
3292}
3293
3306void nbgl_useCaseGenericSettings(const char *appName,
3307 uint8_t initPage,
3308 const nbgl_genericContents_t *settingContents,
3309 const nbgl_contentInfoList_t *infosList,
3310 nbgl_callback_t quitCallback)
3311{
3312 reset_callbacks_and_context();
3313
3314 // memorize context
3315 onQuit = quitCallback;
3316 pageTitle = appName;
3317 navType = GENERIC_NAV;
3318
3319 if (settingContents != NULL) {
3320 memcpy(&genericContext.genericContents, settingContents, sizeof(nbgl_genericContents_t));
3321 }
3322 if (infosList != NULL) {
3323 genericContext.hasFinishingContent = true;
3324 memset(&FINISHING_CONTENT, 0, sizeof(nbgl_content_t));
3325 FINISHING_CONTENT.type = INFOS_LIST;
3326 memcpy(&FINISHING_CONTENT.content, infosList, sizeof(nbgl_content_u));
3327 }
3328
3329 // fill navigation structure
3330 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3331 if (infosList != NULL) {
3332 nbPages += getNbPagesForContent(&FINISHING_CONTENT, nbPages, true, false);
3333 }
3334
3335 prepareNavInfo(false, nbPages, NULL);
3336
3337 displayGenericContextPage(initPage, true);
3338}
3339
3351void nbgl_useCaseGenericConfiguration(const char *title,
3352 uint8_t initPage,
3353 const nbgl_genericContents_t *contents,
3354 nbgl_callback_t quitCallback)
3355{
3356 nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
3357}
3358
3376 const char *appName,
3377 const nbgl_icon_details_t *appIcon,
3378 const char *tagline,
3379 const uint8_t
3380 initSettingPage, // if not INIT_HOME_PAGE, start directly the corresponding setting page
3381 const nbgl_genericContents_t *settingContents,
3382 const nbgl_contentInfoList_t *infosList,
3383 const nbgl_homeAction_t *action, // Set to NULL if no additional action
3384 nbgl_callback_t quitCallback)
3385{
3386 nbgl_homeAndSettingsContext_t *context = &bundleNavContext.homeAndSettings;
3387
3388 reset_callbacks_and_context();
3389
3390 context->appName = appName;
3391 context->appIcon = appIcon;
3392 context->tagline = tagline;
3393 context->settingContents = settingContents;
3394 context->infosList = infosList;
3395 if (action != NULL) {
3396 memcpy(&context->homeAction, action, sizeof(nbgl_homeAction_t));
3397 }
3398 else {
3399 memset(&context->homeAction, 0, sizeof(nbgl_homeAction_t));
3400 }
3401 context->quitCallback = quitCallback;
3402
3403 if (initSettingPage != INIT_HOME_PAGE) {
3404 bundleNavStartSettingsAtPage(initSettingPage);
3405 }
3406 else {
3407 bundleNavStartHome();
3408 }
3409}
3410
3418void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
3419{
3420 nbgl_screenTickerConfiguration_t ticker = {.tickerCallback = &tickerCallback,
3421 .tickerIntervale = 0, // not periodic
3422 .tickerValue = STATUS_SCREEN_DURATION};
3423 nbgl_pageInfoDescription_t info = {0};
3424
3425 reset_callbacks_and_context();
3426
3427 onQuit = quitCallback;
3428 if (isSuccess) {
3429#ifdef HAVE_PIEZO_SOUND
3430 os_io_seph_cmd_piezo_play_tune(TUNE_LEDGER_MOMENT);
3431#endif // HAVE_PIEZO_SOUND
3432 }
3433 info.centeredInfo.icon = isSuccess ? &CHECK_CIRCLE_ICON : &DENIED_CIRCLE_ICON;
3435 info.centeredInfo.text1 = message;
3436 info.tapActionText = "";
3437 info.tapActionToken = QUIT_TOKEN;
3438 info.tuneId = TUNE_TAP_CASUAL;
3439 pageContext = nbgl_pageDrawInfo(&pageCallback, &ticker, &info);
3441}
3442
3450 nbgl_callback_t quitCallback)
3451{
3452 const char *msg;
3453 bool isSuccess;
3454 switch (reviewStatusType) {
3456 msg = "Operation signed";
3457 isSuccess = true;
3458 break;
3460 msg = "Operation rejected";
3461 isSuccess = false;
3462 break;
3464 msg = "Transaction signed";
3465 isSuccess = true;
3466 break;
3468 msg = "Transaction rejected";
3469 isSuccess = false;
3470 break;
3472 msg = "Message signed";
3473 isSuccess = true;
3474 break;
3476 msg = "Message rejected";
3477 isSuccess = false;
3478 break;
3480 msg = "Address verified";
3481 isSuccess = true;
3482 break;
3484 msg = "Address verification\ncancelled";
3485 isSuccess = false;
3486 break;
3487 default:
3488 return;
3489 }
3490 nbgl_useCaseStatus(msg, isSuccess, quitCallback);
3491}
3492
3506 const char *message,
3507 const char *subMessage,
3508 const char *confirmText,
3509 const char *cancelText,
3510 nbgl_choiceCallback_t callback)
3511{
3513 // check params
3514 if ((confirmText == NULL) || (cancelText == NULL)) {
3515 return;
3516 }
3517 reset_callbacks_and_context();
3518
3519 info.cancelText = cancelText;
3520 info.centeredInfo.text1 = message;
3521 info.centeredInfo.text2 = subMessage;
3523 info.centeredInfo.icon = icon;
3524 info.confirmationText = confirmText;
3525 info.confirmationToken = CHOICE_TOKEN;
3526 info.tuneId = TUNE_TAP_CASUAL;
3527
3528 onChoice = callback;
3529 pageContext = nbgl_pageDrawConfirmation(&pageCallback, &info);
3531}
3532
3548 const char *message,
3549 const char *subMessage,
3550 const char *confirmText,
3551 const char *cancelText,
3552 nbgl_genericDetails_t *details,
3553 nbgl_choiceCallback_t callback)
3554{
3555 nbgl_layoutDescription_t layoutDescription;
3556 nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = cancelText,
3557 .token = CHOICE_TOKEN,
3558 .topText = confirmText,
3559 .style = ROUNDED_AND_FOOTER_STYLE,
3560 .tuneId = TUNE_TAP_CASUAL};
3561 nbgl_contentCenter_t centeredInfo = {0};
3562 nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
3563 .separationLine = false,
3564 .emptySpace.height = MEDIUM_CENTERING_HEADER};
3565
3566 // check params
3567 if ((confirmText == NULL) || (cancelText == NULL)) {
3568 return;
3569 }
3570
3571 reset_callbacks_and_context();
3572
3573 onChoice = callback;
3574 layoutDescription.modal = false;
3575 layoutDescription.withLeftBorder = true;
3576
3577 layoutDescription.onActionCallback = layoutTouchCallback;
3578 layoutDescription.tapActionText = NULL;
3579
3580 layoutDescription.ticker.tickerCallback = NULL;
3581 sharedContext.usage = SHARE_CTX_CHOICE_WITH_DETAILS;
3582 choiceWithDetailsCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
3583 choiceWithDetailsCtx.details = details;
3584
3585 nbgl_layoutAddHeader(choiceWithDetailsCtx.layoutCtx, &headerDesc);
3586 nbgl_layoutAddChoiceButtons(choiceWithDetailsCtx.layoutCtx, &buttonsInfo);
3587 centeredInfo.icon = icon;
3588 centeredInfo.title = message;
3589 centeredInfo.description = subMessage;
3590 nbgl_layoutAddContentCenter(choiceWithDetailsCtx.layoutCtx, &centeredInfo);
3591
3592 if (details != NULL) {
3594 choiceWithDetailsCtx.layoutCtx, &SEARCH_ICON, CHOICE_DETAILS_TOKEN, TUNE_TAP_CASUAL);
3595 }
3596
3597 nbgl_layoutDraw(choiceWithDetailsCtx.layoutCtx);
3599}
3600
3614void nbgl_useCaseConfirm(const char *message,
3615 const char *subMessage,
3616 const char *confirmText,
3617 const char *cancelText,
3618 nbgl_callback_t callback)
3619{
3620 // Don't reset callback or nav context as this is just a modal.
3621
3622 nbgl_pageConfirmationDescription_t info = {.cancelText = cancelText,
3623 .centeredInfo.text1 = message,
3624 .centeredInfo.text2 = subMessage,
3625 .centeredInfo.text3 = NULL,
3626 .centeredInfo.style = LARGE_CASE_INFO,
3627 .centeredInfo.icon = &IMPORTANT_CIRCLE_ICON,
3628 .centeredInfo.offsetY = 0,
3629 .confirmationText = confirmText,
3630 .confirmationToken = CHOICE_TOKEN,
3631 .tuneId = TUNE_TAP_CASUAL,
3632 .modal = true};
3633 onModalConfirm = callback;
3634 if (modalPageContext != NULL) {
3635 nbgl_pageRelease(modalPageContext);
3636 }
3637 modalPageContext = nbgl_pageDrawConfirmation(&pageModalCallback, &info);
3639}
3640
3652 const char *message,
3653 const char *actionText,
3654 nbgl_callback_t callback)
3655{
3656 nbgl_pageContent_t content = {0};
3657
3658 reset_callbacks_and_context();
3659 // memorize callback
3660 onAction = callback;
3661
3662 content.tuneId = TUNE_TAP_CASUAL;
3663 content.type = INFO_BUTTON;
3664 content.infoButton.buttonText = actionText;
3665 content.infoButton.text = message;
3666 content.infoButton.icon = icon;
3667 content.infoButton.buttonToken = ACTION_BUTTON_TOKEN;
3668
3669 pageContext = nbgl_pageDrawGenericContent(&pageCallback, NULL, &content);
3671}
3672
3685 const char *reviewTitle,
3686 const char *reviewSubTitle,
3687 const char *rejectText,
3688 nbgl_callback_t continueCallback,
3689 nbgl_callback_t rejectCallback)
3690{
3691 nbgl_pageInfoDescription_t info = {.footerText = rejectText,
3692 .footerToken = QUIT_TOKEN,
3693 .tapActionText = NULL,
3694 .isSwipeable = true,
3695 .tapActionToken = CONTINUE_TOKEN,
3696 .topRightStyle = NO_BUTTON_STYLE,
3697 .actionButtonText = NULL,
3698 .tuneId = TUNE_TAP_CASUAL};
3699
3700 reset_callbacks_and_context();
3701
3702 info.centeredInfo.icon = icon;
3703 info.centeredInfo.text1 = reviewTitle;
3704 info.centeredInfo.text2 = reviewSubTitle;
3705 info.centeredInfo.text3 = "Swipe to review";
3707 info.centeredInfo.offsetY = 0;
3708 onQuit = rejectCallback;
3709 onContinue = continueCallback;
3710
3711#ifdef HAVE_PIEZO_SOUND
3712 // Play notification sound
3713 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
3714#endif // HAVE_PIEZO_SOUND
3715
3716 pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info);
3717 nbgl_refresh();
3718}
3719
3724void nbgl_useCaseRegularReview(uint8_t initPage,
3725 uint8_t nbPages,
3726 const char *rejectText,
3727 nbgl_layoutTouchCallback_t buttonCallback,
3728 nbgl_navCallback_t navCallback,
3729 nbgl_choiceCallback_t choiceCallback)
3730{
3731 reset_callbacks_and_context();
3732
3733 // memorize context
3734 onChoice = choiceCallback;
3735 onNav = navCallback;
3736 onControls = buttonCallback;
3737 forwardNavOnly = false;
3738 navType = REVIEW_NAV;
3739
3740 // fill navigation structure
3741 UNUSED(rejectText);
3742 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3743
3744 displayReviewPage(initPage, true);
3745}
3746
3760 const nbgl_pageInfoLongPress_t *infoLongPress,
3761 const char *rejectText,
3762 nbgl_choiceCallback_t callback)
3763{
3764 uint8_t offset = 0;
3765
3766 reset_callbacks_and_context();
3767
3768 // memorize context
3769 onChoice = callback;
3770 navType = GENERIC_NAV;
3771 pageTitle = NULL;
3772 bundleNavContext.review.operationType = TYPE_OPERATION;
3773
3774 genericContext.genericContents.contentsList = localContentsList;
3775 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3776
3777 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3778 localContentsList[offset].type = TAG_VALUE_LIST;
3779 memcpy(&localContentsList[offset].content.tagValueList,
3780 tagValueList,
3782 offset++;
3783 }
3784
3785 localContentsList[offset].type = INFO_LONG_PRESS;
3786 memcpy(&localContentsList[offset].content.infoLongPress,
3787 infoLongPress,
3788 sizeof(nbgl_pageInfoLongPress_t));
3789 localContentsList[offset].content.infoLongPress.longPressToken = CONFIRM_TOKEN;
3790 offset++;
3791
3792 genericContext.genericContents.nbContents = offset;
3793
3794 // compute number of pages & fill navigation structure
3795 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3796 UNUSED(rejectText);
3797 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3798
3799 displayGenericContextPage(0, true);
3800}
3801
3816 const nbgl_pageInfoLongPress_t *infoLongPress,
3817 const char *rejectText,
3818 nbgl_choiceCallback_t callback)
3819{
3820 uint8_t offset = 0;
3821
3822 reset_callbacks_and_context();
3823
3824 // memorize context
3825 onChoice = callback;
3826 navType = GENERIC_NAV;
3827 pageTitle = NULL;
3828
3829 genericContext.genericContents.contentsList = localContentsList;
3830 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
3831
3832 if (tagValueList != NULL && tagValueList->nbPairs != 0) {
3833 localContentsList[offset].type = TAG_VALUE_LIST;
3834 memcpy(&localContentsList[offset].content.tagValueList,
3835 tagValueList,
3837 offset++;
3838 }
3839
3840 localContentsList[offset].type = INFO_BUTTON;
3841 localContentsList[offset].content.infoButton.text = infoLongPress->text;
3842 localContentsList[offset].content.infoButton.icon = infoLongPress->icon;
3843 localContentsList[offset].content.infoButton.buttonText = infoLongPress->longPressText;
3844 localContentsList[offset].content.infoButton.buttonToken = CONFIRM_TOKEN;
3845 localContentsList[offset].content.infoButton.tuneId = TUNE_TAP_CASUAL;
3846 offset++;
3847
3848 genericContext.genericContents.nbContents = offset;
3849
3850 // compute number of pages & fill navigation structure
3851 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
3852 UNUSED(rejectText);
3853 prepareNavInfo(true, nbPages, getRejectReviewText(TYPE_OPERATION));
3854
3855 displayGenericContextPage(0, true);
3856}
3857
3874void nbgl_useCaseReview(nbgl_operationType_t operationType,
3875 const nbgl_contentTagValueList_t *tagValueList,
3876 const nbgl_icon_details_t *icon,
3877 const char *reviewTitle,
3878 const char *reviewSubTitle,
3879 const char *finishTitle,
3880 nbgl_choiceCallback_t choiceCallback)
3881{
3882 reset_callbacks_and_context();
3883
3884 useCaseReview(operationType,
3885 tagValueList,
3886 icon,
3887 reviewTitle,
3888 reviewSubTitle,
3889 finishTitle,
3890 NULL,
3891 choiceCallback,
3892 false,
3893 true);
3894}
3895
3916 const nbgl_contentTagValueList_t *tagValueList,
3917 const nbgl_icon_details_t *icon,
3918 const char *reviewTitle,
3919 const char *reviewSubTitle,
3920 const char *finishTitle,
3921 const nbgl_tipBox_t *tipBox,
3922 nbgl_choiceCallback_t choiceCallback)
3923{
3924 nbgl_useCaseAdvancedReview(operationType,
3925 tagValueList,
3926 icon,
3927 reviewTitle,
3928 reviewSubTitle,
3929 finishTitle,
3930 tipBox,
3931 &blindSigningWarning,
3932 choiceCallback);
3933}
3934
3958 const nbgl_contentTagValueList_t *tagValueList,
3959 const nbgl_icon_details_t *icon,
3960 const char *reviewTitle,
3961 const char *reviewSubTitle,
3962 const char *finishTitle,
3963 const nbgl_tipBox_t *tipBox,
3964 const nbgl_warning_t *warning,
3965 nbgl_choiceCallback_t choiceCallback)
3966{
3967 reset_callbacks_and_context();
3968
3969 // memorize tipBox because it can be in the call stack of the caller
3970 if (tipBox != NULL) {
3971 memcpy(&activeTipBox, tipBox, sizeof(activeTipBox));
3972 }
3973 // if no warning at all, it's a simple review
3974 if ((warning == NULL)
3975 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
3976 && (warning->reviewDetails == NULL))) {
3977 useCaseReview(operationType,
3978 tagValueList,
3979 icon,
3980 reviewTitle,
3981 reviewSubTitle,
3982 finishTitle,
3983 tipBox,
3984 choiceCallback,
3985 false,
3986 true);
3987 return;
3988 }
3989 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
3990 operationType |= NO_THREAT_OPERATION;
3991 }
3992 else {
3993 operationType |= RISKY_OPERATION;
3994 }
3995 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
3996 reviewWithWarnCtx.isStreaming = false;
3997 reviewWithWarnCtx.operationType = operationType;
3998 reviewWithWarnCtx.tagValueList = tagValueList;
3999 reviewWithWarnCtx.icon = icon;
4000 reviewWithWarnCtx.reviewTitle = reviewTitle;
4001 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4002 reviewWithWarnCtx.finishTitle = finishTitle;
4003 reviewWithWarnCtx.warning = warning;
4004 reviewWithWarnCtx.choiceCallback = choiceCallback;
4005
4006 // display the initial warning only of a risk/threat or blind signing
4007 if ((!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
4008 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
4009 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)))
4010 && (warning->introDetails == NULL)) {
4011 useCaseReview(operationType,
4012 tagValueList,
4013 icon,
4014 reviewTitle,
4015 reviewSubTitle,
4016 finishTitle,
4017 tipBox,
4018 choiceCallback,
4019 false,
4020 true);
4021 return;
4022 }
4023
4024 displayInitialWarning();
4025}
4026
4044 const nbgl_contentTagValueList_t *tagValueList,
4045 const nbgl_icon_details_t *icon,
4046 const char *reviewTitle,
4047 const char *reviewSubTitle,
4048 const char *finishTitle,
4049 nbgl_choiceCallback_t choiceCallback)
4050{
4051 reset_callbacks_and_context();
4052
4053 useCaseReview(operationType,
4054 tagValueList,
4055 icon,
4056 reviewTitle,
4057 reviewSubTitle,
4058 finishTitle,
4059 NULL,
4060 choiceCallback,
4061 true,
4062 true);
4063}
4064
4074 const char *rejectText,
4075 nbgl_callback_t rejectCallback)
4076{
4077 reset_callbacks_and_context();
4078
4079 // memorize context
4080 onQuit = rejectCallback;
4081 navType = GENERIC_NAV;
4082 pageTitle = NULL;
4083 bundleNavContext.review.operationType = TYPE_OPERATION;
4084
4085 memcpy(&genericContext.genericContents, contents, sizeof(nbgl_genericContents_t));
4086
4087 // compute number of pages & fill navigation structure
4088 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4089 prepareNavInfo(true, nbPages, rejectText);
4090 navInfo.quitToken = QUIT_TOKEN;
4091
4092#ifdef HAVE_PIEZO_SOUND
4093 // Play notification sound
4094 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4095#endif // HAVE_PIEZO_SOUND
4096
4097 displayGenericContextPage(0, true);
4098}
4099
4113 const nbgl_icon_details_t *icon,
4114 const char *reviewTitle,
4115 const char *reviewSubTitle,
4116 nbgl_choiceCallback_t choiceCallback)
4117{
4118 reset_callbacks_and_context();
4119
4120 useCaseReviewStreamingStart(
4121 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4122}
4123
4138 const nbgl_icon_details_t *icon,
4139 const char *reviewTitle,
4140 const char *reviewSubTitle,
4141 nbgl_choiceCallback_t choiceCallback)
4142{
4144 operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
4145}
4146
4163 const nbgl_icon_details_t *icon,
4164 const char *reviewTitle,
4165 const char *reviewSubTitle,
4166 const nbgl_warning_t *warning,
4167 nbgl_choiceCallback_t choiceCallback)
4168{
4169 reset_callbacks_and_context();
4170
4171 // if no warning at all, it's a simple review
4172 if ((warning == NULL)
4173 || ((warning->predefinedSet == 0) && (warning->introDetails == NULL)
4174 && (warning->reviewDetails == NULL))) {
4175 useCaseReviewStreamingStart(
4176 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4177 return;
4178 }
4179 if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) {
4180 operationType |= NO_THREAT_OPERATION;
4181 }
4182 else {
4183 operationType |= RISKY_OPERATION;
4184 }
4185
4186 sharedContext.usage = SHARE_CTX_REVIEW_WITH_WARNING;
4187 reviewWithWarnCtx.isStreaming = true;
4188 reviewWithWarnCtx.operationType = operationType;
4189 reviewWithWarnCtx.icon = icon;
4190 reviewWithWarnCtx.reviewTitle = reviewTitle;
4191 reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
4192 reviewWithWarnCtx.choiceCallback = choiceCallback;
4193 reviewWithWarnCtx.warning = warning;
4194
4195 // display the initial warning only of a risk/threat or blind signing
4196 if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN))
4197 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN))
4198 && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) {
4199 useCaseReviewStreamingStart(
4200 operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true);
4201 return;
4202 }
4203 displayInitialWarning();
4204}
4205
4220 nbgl_choiceCallback_t choiceCallback,
4221 nbgl_callback_t skipCallback)
4222{
4223 // Should follow a call to nbgl_useCaseReviewStreamingStart
4224 memset(&genericContext, 0, sizeof(genericContext));
4225
4226 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4227 bundleNavContext.reviewStreaming.skipCallback = skipCallback;
4228
4229 // memorize context
4230 onChoice = bundleNavReviewStreamingChoice;
4231 navType = STREAMING_NAV;
4232 pageTitle = NULL;
4233
4234 genericContext.genericContents.contentsList = localContentsList;
4235 genericContext.genericContents.nbContents = 1;
4236 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4237
4238 // Then the tag/value pairs
4239 STARTING_CONTENT.type = TAG_VALUE_LIST;
4240 memcpy(
4241 &STARTING_CONTENT.content.tagValueList, tagValueList, sizeof(nbgl_contentTagValueList_t));
4242
4243 // compute number of pages & fill navigation structure
4244 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4245 &genericContext.genericContents,
4246 0,
4247 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4248 prepareNavInfo(true,
4250 getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4251 // if the operation is skippable
4252 if (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION) {
4253 navInfo.progressIndicator = false;
4254 navInfo.skipText = "Skip";
4255 navInfo.skipToken = SKIP_TOKEN;
4256 }
4257
4258 displayGenericContextPage(0, true);
4259}
4260
4272 nbgl_choiceCallback_t choiceCallback)
4273{
4274 nbgl_useCaseReviewStreamingContinueExt(tagValueList, choiceCallback, NULL);
4275}
4276
4285void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
4286 nbgl_choiceCallback_t choiceCallback)
4287{
4288 // Should follow a call to nbgl_useCaseReviewStreamingContinue
4289 memset(&genericContext, 0, sizeof(genericContext));
4290
4291 bundleNavContext.reviewStreaming.choiceCallback = choiceCallback;
4292
4293 // memorize context
4294 onChoice = bundleNavReviewStreamingChoice;
4295 navType = STREAMING_NAV;
4296 pageTitle = NULL;
4297
4298 genericContext.genericContents.contentsList = localContentsList;
4299 genericContext.genericContents.nbContents = 1;
4300 memset(localContentsList, 0, 1 * sizeof(nbgl_content_t));
4301
4302 // Eventually the long press page
4303 STARTING_CONTENT.type = INFO_LONG_PRESS;
4304 prepareReviewLastPage(bundleNavContext.reviewStreaming.operationType,
4305 &STARTING_CONTENT.content.infoLongPress,
4306 bundleNavContext.reviewStreaming.icon,
4307 finishTitle);
4308
4309 // compute number of pages & fill navigation structure
4310 bundleNavContext.reviewStreaming.stepPageNb = getNbPagesForGenericContents(
4311 &genericContext.genericContents,
4312 0,
4313 (bundleNavContext.reviewStreaming.operationType & SKIPPABLE_OPERATION));
4314 prepareNavInfo(true, 1, getRejectReviewText(bundleNavContext.reviewStreaming.operationType));
4315
4316 displayGenericContextPage(0, true);
4317}
4318
4323void nbgl_useCaseAddressConfirmationExt(const char *address,
4324 nbgl_choiceCallback_t callback,
4325 const nbgl_contentTagValueList_t *tagValueList)
4326{
4327 reset_callbacks_and_context();
4328 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4329
4330 // save context
4331 onChoice = callback;
4332 navType = GENERIC_NAV;
4333 pageTitle = NULL;
4334
4335 genericContext.genericContents.contentsList = localContentsList;
4336 genericContext.genericContents.nbContents = (tagValueList == NULL) ? 1 : 2;
4337 memset(localContentsList, 0, 2 * sizeof(nbgl_content_t));
4338 prepareAddressConfirmationPages(
4339 address, tagValueList, &STARTING_CONTENT, &localContentsList[1]);
4340
4341 // fill navigation structure, common to all pages
4342 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4343
4344 prepareNavInfo(true, nbPages, "Cancel");
4345
4346#ifdef HAVE_PIEZO_SOUND
4347 // Play notification sound
4348 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4349#endif // HAVE_PIEZO_SOUND
4350
4351 displayGenericContextPage(0, true);
4352}
4353
4370void nbgl_useCaseAddressReview(const char *address,
4371 const nbgl_contentTagValueList_t *additionalTagValueList,
4372 const nbgl_icon_details_t *icon,
4373 const char *reviewTitle,
4374 const char *reviewSubTitle,
4375 nbgl_choiceCallback_t choiceCallback)
4376{
4377 reset_callbacks_and_context();
4378
4379 // release a potential modal
4380 if (addressConfirmationContext.modalLayout) {
4381 nbgl_layoutRelease(addressConfirmationContext.modalLayout);
4382 }
4383 memset(&addressConfirmationContext, 0, sizeof(addressConfirmationContext));
4384
4385 // save context
4386 onChoice = choiceCallback;
4387 navType = GENERIC_NAV;
4388 pageTitle = NULL;
4389 bundleNavContext.review.operationType = TYPE_OPERATION;
4390
4391 genericContext.genericContents.contentsList = localContentsList;
4392 memset(localContentsList, 0, 3 * sizeof(nbgl_content_t));
4393
4394 // First a centered info
4395 STARTING_CONTENT.type = EXTENDED_CENTER;
4396 prepareReviewFirstPage(
4397 &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
4398 STARTING_CONTENT.content.extendedCenter.contentCenter.subText = "Swipe to continue";
4399
4400 // Then the address confirmation pages
4401 prepareAddressConfirmationPages(
4402 address, additionalTagValueList, &localContentsList[1], &localContentsList[2]);
4403
4404 // fill navigation structure, common to all pages
4405 genericContext.genericContents.nbContents
4406 = (localContentsList[2].type == TAG_VALUE_CONFIRM) ? 3 : 2;
4407 uint8_t nbPages = getNbPagesForGenericContents(&genericContext.genericContents, 0, false);
4408
4409 prepareNavInfo(true, nbPages, "Cancel");
4410
4411#ifdef HAVE_PIEZO_SOUND
4412 // Play notification sound
4413 os_io_seph_cmd_piezo_play_tune(TUNE_LOOK_AT_ME);
4414#endif // HAVE_PIEZO_SOUND
4415
4416 displayGenericContextPage(0, true);
4417}
4418
4427void nbgl_useCaseSpinner(const char *text)
4428{
4429 // if the previous Use Case was not Spinner, fresh start
4430 if (genericContext.type != USE_CASE_SPINNER) {
4431 memset(&genericContext, 0, sizeof(genericContext));
4432 genericContext.type = USE_CASE_SPINNER;
4433 nbgl_layoutDescription_t layoutDescription = {0};
4434
4435 layoutDescription.withLeftBorder = true;
4436
4437 genericContext.backgroundLayout = nbgl_layoutGet(&layoutDescription);
4438
4440 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4441
4442 nbgl_layoutDraw(genericContext.backgroundLayout);
4444 }
4445 else {
4446 // otherwise increment spinner
4447 genericContext.spinnerPosition++;
4448 // there are only NB_SPINNER_POSITIONSpositions
4449 if (genericContext.spinnerPosition == NB_SPINNER_POSITIONS) {
4450 genericContext.spinnerPosition = 0;
4451 }
4452 int ret = nbgl_layoutUpdateSpinner(
4453 genericContext.backgroundLayout, text, NULL, genericContext.spinnerPosition);
4454 if (ret == 1) {
4456 }
4457 else if (ret == 2) {
4459 }
4460 }
4461}
4462
4463#ifdef NBGL_KEYPAD
4482void nbgl_useCaseKeypad(const char *title,
4483 uint8_t minDigits,
4484 uint8_t maxDigits,
4485 bool shuffled,
4486 bool hidden,
4487 nbgl_pinValidCallback_t validatePinCallback,
4488 nbgl_callback_t backCallback)
4489{
4490 nbgl_layoutDescription_t layoutDescription = {0};
4492 .separationLine = true,
4493 .backAndText.token = BACK_TOKEN,
4494 .backAndText.tuneId = TUNE_TAP_CASUAL,
4495 .backAndText.text = NULL};
4496 int status = -1;
4497
4498 if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) {
4499 return;
4500 }
4501
4502 reset_callbacks_and_context();
4503 // reset the keypad context
4504 memset(&keypadContext, 0, sizeof(KeypadContext_t));
4505
4506 // memorize context
4507 onQuit = backCallback;
4508
4509 // get a layout
4510 layoutDescription.onActionCallback = keypadGeneric_cb;
4511 layoutDescription.modal = false;
4512 layoutDescription.withLeftBorder = false;
4513 keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription);
4514 keypadContext.hidden = hidden;
4515
4516 // set back key in header
4517 nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc);
4518
4519 // add keypad
4520 status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled);
4521 if (status < 0) {
4522 return;
4523 }
4524 // add keypad content
4526 keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, "");
4527
4528 if (status < 0) {
4529 return;
4530 }
4531
4532 // validation pin callback
4533 onValidatePin = validatePinCallback;
4534 // pin code acceptable lengths
4535 keypadContext.pinMinDigits = minDigits;
4536 keypadContext.pinMaxDigits = maxDigits;
4537
4538 nbgl_layoutDraw(keypadContext.layoutCtx);
4540}
4541#endif // NBGL_KEYPAD
4542
4543#endif // HAVE_SE_TOUCH
4544#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:567
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:727
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:1720
#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:1730
#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:1746
#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)
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)
#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_useCaseKeypad(const char *title, uint8_t minDigits, uint8_t maxDigits, bool shuffled, bool hidden, nbgl_pinValidCallback_t validatePinCallback, nbgl_callback_t backCallback)
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
uint8_t nbMaxLinesForValue
if > 0, set the max number of lines for value field.
bool hideEndOfLastLine
if set to true, replace 3 last chars of last line by "..."
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